feat: lookup path (builtins.findFile)
This commit is contained in:
36
Cargo.lock
generated
36
Cargo.lock
generated
@@ -1927,11 +1927,11 @@ dependencies = [
|
||||
"rnix",
|
||||
"rowan",
|
||||
"rusqlite",
|
||||
"rust-embed",
|
||||
"rustyline",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sourcemap",
|
||||
"string-interner",
|
||||
"tar",
|
||||
"tempfile",
|
||||
@@ -2587,6 +2587,40 @@ dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "8.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27"
|
||||
dependencies = [
|
||||
"rust-embed-impl",
|
||||
"rust-embed-utils",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-impl"
|
||||
version = "8.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rust-embed-utils",
|
||||
"syn",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-utils"
|
||||
version = "8.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1"
|
||||
dependencies = [
|
||||
"sha2",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.27"
|
||||
|
||||
@@ -30,6 +30,8 @@ hashbrown = "0.16"
|
||||
petgraph = "0.8"
|
||||
string-interner = "0.19"
|
||||
|
||||
rust-embed="8.11"
|
||||
|
||||
itertools = "0.14"
|
||||
|
||||
regex = "1.11"
|
||||
@@ -41,7 +43,6 @@ nix-nar = "0.3"
|
||||
sha2 = "0.10"
|
||||
hex = "0.4"
|
||||
|
||||
sourcemap = "9.0"
|
||||
base64 = "0.22"
|
||||
|
||||
# Fetcher dependencies
|
||||
|
||||
@@ -8,7 +8,7 @@ import { force } from "../thunk";
|
||||
import { type NixStringContext, mkStringWithContext, addBuiltContext } from "../string-context";
|
||||
import { forceFunction } from "../type-assert";
|
||||
import { nixValueToJson } from "../conversion";
|
||||
import { typeOf } from "./type-check";
|
||||
import { isAttrs, isPath, typeOf } from "./type-check";
|
||||
|
||||
const convertJsonToNix = (json: unknown): NixValue => {
|
||||
if (json === null) {
|
||||
@@ -283,6 +283,16 @@ export const coerceToStringWithContext = (
|
||||
* - Preserves string context if present
|
||||
*/
|
||||
export const coerceToPath = (value: NixValue, outContext?: NixStringContext): string => {
|
||||
const forced = force(value);
|
||||
|
||||
if (isPath(forced)) {
|
||||
return forced.value;
|
||||
}
|
||||
if (isAttrs(forced) && Object.hasOwn(forced, "__toString")) {
|
||||
const toStringFunc = forceFunction(forced.__toString);
|
||||
return coerceToPath(toStringFunc(forced), outContext);
|
||||
}
|
||||
|
||||
const pathStr = coerceToString(value, StringCoercionMode.Base, false, outContext);
|
||||
|
||||
if (pathStr === "") {
|
||||
|
||||
@@ -3,15 +3,16 @@
|
||||
* Implemented via Rust ops exposed through deno_core
|
||||
*/
|
||||
|
||||
import { forceAttrs, forceBool, forceStringValue } from "../type-assert";
|
||||
import type { NixValue, NixAttrs } from "../types";
|
||||
import { isNixPath } from "../types";
|
||||
import { forceAttrs, forceBool, forceList, forceStringNoCtx, forceStringValue } from "../type-assert";
|
||||
import type { NixValue, NixAttrs, NixPath } from "../types";
|
||||
import { isNixPath, IS_PATH, CatchableError } from "../types";
|
||||
import { force } from "../thunk";
|
||||
import { coerceToPath, coerceToString, StringCoercionMode } from "./conversion";
|
||||
import { getPathValue } from "../path";
|
||||
import type { NixStringContext, StringWithContext } from "../string-context";
|
||||
import { mkStringWithContext } from "../string-context";
|
||||
import { isPath } from "./type-check";
|
||||
import { getCorepkg } from "../corepkgs";
|
||||
|
||||
export const importFunc = (path: NixValue): NixValue => {
|
||||
const context: NixStringContext = new Set();
|
||||
@@ -390,10 +391,70 @@ export const filterSource = (args: NixValue): never => {
|
||||
throw new Error("Not implemented: filterSource");
|
||||
};
|
||||
|
||||
const suffixIfPotentialMatch = (prefix: string, path: string): string | null => {
|
||||
const n = prefix.length;
|
||||
|
||||
const needSeparator = n > 0 && n < path.length;
|
||||
|
||||
if (needSeparator && path[n] !== "/") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!path.startsWith(prefix)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return needSeparator ? path.substring(n + 1) : path.substring(n);
|
||||
};
|
||||
|
||||
export const findFile =
|
||||
(search: NixValue) =>
|
||||
(lookup: NixValue): never => {
|
||||
throw new Error("Not implemented: findFile");
|
||||
(searchPath: NixValue) =>
|
||||
(lookupPath: NixValue): NixPath => {
|
||||
const forcedSearchPath = forceList(searchPath);
|
||||
const lookupPathStr = forceStringNoCtx(lookupPath);
|
||||
|
||||
for (const item of forcedSearchPath) {
|
||||
const attrs = forceAttrs(item);
|
||||
|
||||
const prefix = "prefix" in attrs ? forceStringNoCtx(attrs.prefix) : "";
|
||||
|
||||
if (!("path" in attrs)) {
|
||||
throw new Error("findFile: search path element is missing 'path' attribute");
|
||||
}
|
||||
|
||||
const suffix = suffixIfPotentialMatch(prefix, lookupPathStr);
|
||||
if (suffix === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const context: NixStringContext = new Set();
|
||||
const pathVal = coerceToString(attrs.path, StringCoercionMode.Interpolation, false, context);
|
||||
|
||||
if (context.size > 0) {
|
||||
throw new Error("findFile: path with string context is not yet supported");
|
||||
}
|
||||
|
||||
const resolvedPath = Deno.core.ops.op_resolve_path(pathVal, "");
|
||||
const candidatePath = suffix.length > 0
|
||||
? Deno.core.ops.op_resolve_path(suffix, resolvedPath)
|
||||
: resolvedPath;
|
||||
|
||||
if (Deno.core.ops.op_path_exists(candidatePath)) {
|
||||
return { [IS_PATH]: true, value: candidatePath };
|
||||
}
|
||||
}
|
||||
|
||||
if (lookupPathStr.startsWith("nix/")) {
|
||||
const corepkgName = lookupPathStr.substring(4);
|
||||
const corepkgContent = getCorepkg(corepkgName);
|
||||
|
||||
if (corepkgContent !== undefined) {
|
||||
// FIXME: special path type
|
||||
return { [IS_PATH]: true, value: `<nix/${corepkgName}>` };
|
||||
}
|
||||
}
|
||||
|
||||
throw new CatchableError(`file '${lookupPathStr}' was not found in the Nix search path`);
|
||||
};
|
||||
|
||||
export const getEnv = (s: NixValue): string => {
|
||||
|
||||
73
nix-js/runtime-ts/src/corepkgs/fetchurl.nix.ts
Normal file
73
nix-js/runtime-ts/src/corepkgs/fetchurl.nix.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
export const FETCHURL_NIX = `{
|
||||
system ? "", # obsolete
|
||||
url,
|
||||
hash ? "", # an SRI hash
|
||||
|
||||
# Legacy hash specification
|
||||
md5 ? "",
|
||||
sha1 ? "",
|
||||
sha256 ? "",
|
||||
sha512 ? "",
|
||||
outputHash ?
|
||||
if hash != "" then
|
||||
hash
|
||||
else if sha512 != "" then
|
||||
sha512
|
||||
else if sha1 != "" then
|
||||
sha1
|
||||
else if md5 != "" then
|
||||
md5
|
||||
else
|
||||
sha256,
|
||||
outputHashAlgo ?
|
||||
if hash != "" then
|
||||
""
|
||||
else if sha512 != "" then
|
||||
"sha512"
|
||||
else if sha1 != "" then
|
||||
"sha1"
|
||||
else if md5 != "" then
|
||||
"md5"
|
||||
else
|
||||
"sha256",
|
||||
|
||||
executable ? false,
|
||||
unpack ? false,
|
||||
name ? baseNameOf (toString url),
|
||||
impure ? false,
|
||||
}:
|
||||
|
||||
derivation (
|
||||
{
|
||||
builder = "builtin:fetchurl";
|
||||
|
||||
# New-style output content requirements.
|
||||
outputHashMode = if unpack || executable then "recursive" else "flat";
|
||||
|
||||
inherit
|
||||
name
|
||||
url
|
||||
executable
|
||||
unpack
|
||||
;
|
||||
|
||||
system = "builtin";
|
||||
|
||||
# No need to double the amount of network traffic
|
||||
preferLocalBuild = true;
|
||||
|
||||
# This attribute does nothing; it's here to avoid changing evaluation results.
|
||||
impureEnvVars = [
|
||||
"http_proxy"
|
||||
"https_proxy"
|
||||
"ftp_proxy"
|
||||
"all_proxy"
|
||||
"no_proxy"
|
||||
];
|
||||
|
||||
# To make "nix-prefetch-url" work.
|
||||
urls = [ url ];
|
||||
}
|
||||
// (if impure then { __impure = true; } else { inherit outputHashAlgo outputHash; })
|
||||
)
|
||||
`;
|
||||
9
nix-js/runtime-ts/src/corepkgs/index.ts
Normal file
9
nix-js/runtime-ts/src/corepkgs/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { FETCHURL_NIX } from "./fetchurl.nix";
|
||||
|
||||
export const COREPKGS: Record<string, string> = {
|
||||
"fetchurl.nix": FETCHURL_NIX,
|
||||
};
|
||||
|
||||
export const getCorepkg = (name: string): string | undefined => {
|
||||
return COREPKGS[name];
|
||||
};
|
||||
@@ -169,7 +169,15 @@ export const concatStringsWithContext = (parts: NixValue[]): NixString | NixPath
|
||||
* @returns NixPath object with absolute path
|
||||
*/
|
||||
export const resolvePath = (currentDir: string, path: NixValue): NixPath => {
|
||||
const pathStr = forceStringValue(path);
|
||||
const forced = force(path);
|
||||
let pathStr: string;
|
||||
|
||||
if (isNixPath(forced)) {
|
||||
pathStr = forced.value;
|
||||
} else {
|
||||
pathStr = forceStringValue(path);
|
||||
}
|
||||
|
||||
const resolved = Deno.core.ops.op_resolve_path(currentDir, pathStr);
|
||||
return mkPath(resolved);
|
||||
};
|
||||
|
||||
@@ -85,6 +85,17 @@ export const forceString = (value: NixValue): NixString => {
|
||||
throw new TypeError(`Expected string, got ${typeOf(forced)}`);
|
||||
};
|
||||
|
||||
export const forceStringNoCtx = (value: NixValue): string => {
|
||||
const forced = force(value);
|
||||
if (typeof forced === "string") {
|
||||
return forced;
|
||||
}
|
||||
if (isStringWithContext(forced)) {
|
||||
throw new TypeError(`the string '${forced.value}' is not allowed to refer to a store path`)
|
||||
}
|
||||
throw new TypeError(`Expected string, got ${typeOf(forced)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a value and assert it's a boolean
|
||||
* @throws TypeError if value is not a boolean after forcing
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { force, IS_THUNK } from "./thunk";
|
||||
import { type StringWithContext, HAS_CONTEXT, isStringWithContext, getStringContext } from "./string-context";
|
||||
import { op } from "./operators";
|
||||
import { forceAttrs } from "./type-assert";
|
||||
import { forceAttrs, forceStringNoCtx } from "./type-assert";
|
||||
import { isString, typeOf } from "./builtins/type-check";
|
||||
export { HAS_CONTEXT, isStringWithContext };
|
||||
export type { StringWithContext };
|
||||
@@ -81,13 +81,8 @@ export const mkAttrs = (attrs: NixAttrs, keys: NixValue[], values: NixValue[]):
|
||||
if (key === null) {
|
||||
continue;
|
||||
}
|
||||
if (!isString(key)) {
|
||||
throw `Expected string, got ${typeOf(key)}`
|
||||
}
|
||||
if (isStringWithContext(key)) {
|
||||
throw new TypeError(`the string '${key.value}' is not allowed to refer to a store path`)
|
||||
}
|
||||
attrs[key] = values[i];
|
||||
const str = forceStringNoCtx(key);
|
||||
attrs[str] = values[i];
|
||||
}
|
||||
return attrs;
|
||||
};
|
||||
|
||||
@@ -103,7 +103,19 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Path {
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
let expr = if parts.len() == 1 {
|
||||
parts.into_iter().next().unwrap()
|
||||
let part = parts.into_iter().next().unwrap();
|
||||
if let &Ir::Str(Str { ref val, span }) = ctx.get_ir(part)
|
||||
&& let Some(path) = val.strip_prefix("<").map(|path| &path[..path.len() - 1]) {
|
||||
ctx.replace_ir(part, Str { val: path.to_string(), span }.to_ir());
|
||||
let sym = ctx.new_sym("findFile".into());
|
||||
let find_file = ctx.new_expr(Builtin { inner: sym, span }.to_ir());
|
||||
let sym = ctx.new_sym("nixPath".into());
|
||||
let nix_path = ctx.new_expr(Builtin { inner: sym, span }.to_ir());
|
||||
let call = ctx.new_expr(Call { func: find_file, arg: nix_path, span }.to_ir());
|
||||
return Ok(ctx.new_expr(Call { func: call, arg: part, span }.to_ir()));
|
||||
} else {
|
||||
part
|
||||
}
|
||||
} else {
|
||||
ctx.new_expr(ConcatStrings { parts, span }.to_ir())
|
||||
};
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use std::borrow::Cow;
|
||||
use std::marker::PhantomData;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use std::sync::Once;
|
||||
use std::sync::{Arc, Once};
|
||||
|
||||
use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpState, RuntimeOptions, v8};
|
||||
use deno_error::JsErrorClass;
|
||||
use itertools::Itertools as _;
|
||||
use rust_embed::Embed;
|
||||
|
||||
use crate::error::{Error, Result, Source};
|
||||
use crate::store::Store;
|
||||
@@ -96,6 +97,10 @@ mod private {
|
||||
}
|
||||
pub(crate) use private::NixError;
|
||||
|
||||
#[derive(Embed)]
|
||||
#[folder = "src/runtime/corepkgs"]
|
||||
pub(crate) struct CorePkgs;
|
||||
|
||||
#[deno_core::op2]
|
||||
#[string]
|
||||
fn op_import<Ctx: RuntimeContext>(
|
||||
@@ -105,6 +110,24 @@ fn op_import<Ctx: RuntimeContext>(
|
||||
let _span = tracing::info_span!("op_import", path = %path).entered();
|
||||
let ctx: &mut Ctx = state.get_ctx_mut();
|
||||
|
||||
// FIXME: special path type
|
||||
if path.starts_with("<nix/") && path.ends_with(">") {
|
||||
let corepkg_name = &path[5..path.len() - 1];
|
||||
if let Some(file) = CorePkgs::get(corepkg_name) {
|
||||
tracing::info!("Importing corepkg: {}", corepkg_name);
|
||||
let source = Source {
|
||||
ty: crate::error::SourceType::Eval(Arc::new(ctx.get_current_dir().to_path_buf())),
|
||||
src: str::from_utf8(&file.data)
|
||||
.expect("corrupted corepkgs file")
|
||||
.into(),
|
||||
};
|
||||
ctx.add_source(source.clone());
|
||||
return Ok(ctx.compile_code(source).map_err(|err| err.to_string())?);
|
||||
} else {
|
||||
return Err(format!("Corepkg not found: {}", corepkg_name).into());
|
||||
}
|
||||
}
|
||||
|
||||
let current_dir = ctx.get_current_dir();
|
||||
let mut absolute_path = current_dir
|
||||
.join(&path)
|
||||
|
||||
76
nix-js/src/runtime/corepkgs/fetchurl.nix
Normal file
76
nix-js/src/runtime/corepkgs/fetchurl.nix
Normal file
@@ -0,0 +1,76 @@
|
||||
{
|
||||
system ? "", # obsolete
|
||||
url,
|
||||
hash ? "", # an SRI hash
|
||||
|
||||
# Legacy hash specification
|
||||
md5 ? "",
|
||||
sha1 ? "",
|
||||
sha256 ? "",
|
||||
sha512 ? "",
|
||||
outputHash ?
|
||||
if hash != "" then
|
||||
hash
|
||||
else if sha512 != "" then
|
||||
sha512
|
||||
else if sha1 != "" then
|
||||
sha1
|
||||
else if md5 != "" then
|
||||
md5
|
||||
else
|
||||
sha256,
|
||||
outputHashAlgo ?
|
||||
if hash != "" then
|
||||
""
|
||||
else if sha512 != "" then
|
||||
"sha512"
|
||||
else if sha1 != "" then
|
||||
"sha1"
|
||||
else if md5 != "" then
|
||||
"md5"
|
||||
else
|
||||
"sha256",
|
||||
|
||||
executable ? false,
|
||||
unpack ? false,
|
||||
name ? baseNameOf (toString url),
|
||||
# still translates to __impure to trigger derivationStrict error checks.
|
||||
impure ? false,
|
||||
}:
|
||||
|
||||
derivation (
|
||||
{
|
||||
builder = "builtin:fetchurl";
|
||||
|
||||
# New-style output content requirements.
|
||||
outputHashMode = if unpack || executable then "recursive" else "flat";
|
||||
|
||||
inherit
|
||||
name
|
||||
url
|
||||
executable
|
||||
unpack
|
||||
;
|
||||
|
||||
system = "builtin";
|
||||
|
||||
# No need to double the amount of network traffic
|
||||
preferLocalBuild = true;
|
||||
|
||||
impureEnvVars = [
|
||||
# We borrow these environment variables from the caller to allow
|
||||
# easy proxy configuration. This is impure, but a fixed-output
|
||||
# derivation like fetchurl is allowed to do so since its result is
|
||||
# by definition pure.
|
||||
"http_proxy"
|
||||
"https_proxy"
|
||||
"ftp_proxy"
|
||||
"all_proxy"
|
||||
"no_proxy"
|
||||
];
|
||||
|
||||
# To make "nix-prefetch-url" work.
|
||||
urls = [ url ];
|
||||
}
|
||||
// (if impure then { __impure = true; } else { inherit outputHashAlgo outputHash; })
|
||||
)
|
||||
38
nix-js/tests/findfile.rs
Normal file
38
nix-js/tests/findfile.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
mod utils;
|
||||
|
||||
use utils::eval;
|
||||
|
||||
#[test]
|
||||
fn test_find_file_corepkg_fetchurl() {
|
||||
let result = eval(
|
||||
r#"
|
||||
let
|
||||
searchPath = [];
|
||||
lookupPath = "nix/fetchurl.nix";
|
||||
in
|
||||
builtins.findFile searchPath lookupPath
|
||||
"#,
|
||||
);
|
||||
|
||||
assert!(result.to_string().contains("fetchurl.nix"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lookup_path_syntax() {
|
||||
let result = eval(r#"<nix/fetchurl.nix>"#);
|
||||
assert!(result.to_string().contains("fetchurl.nix"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_import_corepkg() {
|
||||
let result = eval(
|
||||
r#"
|
||||
let
|
||||
fetchurl = import <nix/fetchurl.nix>;
|
||||
in
|
||||
builtins.typeOf fetchurl
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_eq!(result.to_string(), "\"lambda\"");
|
||||
}
|
||||
Reference in New Issue
Block a user