From e357678d7014364d8229d6eb858175cc7ecc04bf Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Sun, 15 Feb 2026 16:50:20 +0800 Subject: [PATCH] feat: implement realisePath --- nix-js/runtime-ts/src/builtins/conversion.ts | 20 ++---- nix-js/runtime-ts/src/builtins/functional.ts | 2 +- nix-js/runtime-ts/src/builtins/hash.ts | 4 +- nix-js/runtime-ts/src/builtins/io.ts | 73 +++++++++++--------- nix-js/runtime-ts/src/builtins/path.ts | 24 ++----- 5 files changed, 55 insertions(+), 68 deletions(-) diff --git a/nix-js/runtime-ts/src/builtins/conversion.ts b/nix-js/runtime-ts/src/builtins/conversion.ts index 9ecd2de..a5efd81 100644 --- a/nix-js/runtime-ts/src/builtins/conversion.ts +++ b/nix-js/runtime-ts/src/builtins/conversion.ts @@ -86,7 +86,7 @@ export const coerceToString = ( value: NixValue, mode: StringCoercionMode, copyToStore: boolean = false, - outContext?: NixStringContext, + outContext: NixStringContext, ): string => { const v = force(value); @@ -96,10 +96,8 @@ export const coerceToString = ( } if (isStringWithContext(v)) { - if (outContext) { - for (const elem of v.context) { - outContext.add(elem); - } + for (const elem of v.context) { + outContext.add(elem); } return v.value; } @@ -109,9 +107,7 @@ export const coerceToString = ( if (copyToStore) { const pathStr = v.value; const storePath = Deno.core.ops.op_copy_path_to_store(pathStr); - if (outContext) { - outContext.add(storePath); - } + outContext.add(storePath); return storePath; } return v.value; @@ -253,7 +249,7 @@ export const coerceToStringWithContext = ( * - Returns the path string (not a NixPath object) * - Preserves string context if present */ -export const coerceToPath = (value: NixValue, outContext?: NixStringContext): string => { +export const coerceToPath = (value: NixValue, outContext: NixStringContext): string => { const forced = force(value); if (isPath(forced)) { @@ -347,10 +343,8 @@ export const nixValueToJson = ( return result; } if (isStringWithContext(result)) { - if (outContext) { - for (const elem of result.context) { - outContext.add(elem); - } + for (const elem of result.context) { + outContext.add(elem); } return result.value; } diff --git a/nix-js/runtime-ts/src/builtins/functional.ts b/nix-js/runtime-ts/src/builtins/functional.ts index 18be3a2..6a8b55b 100644 --- a/nix-js/runtime-ts/src/builtins/functional.ts +++ b/nix-js/runtime-ts/src/builtins/functional.ts @@ -41,7 +41,7 @@ export const abort = (s: NixValue): never => { }; export const throwFunc = (s: NixValue): never => { - throw new CatchableError(coerceToString(s, StringCoercionMode.Base)); + throw new CatchableError(coerceToString(s, StringCoercionMode.Base, false, new Set())); }; export const trace = diff --git a/nix-js/runtime-ts/src/builtins/hash.ts b/nix-js/runtime-ts/src/builtins/hash.ts index dabeff5..a8fcee1 100644 --- a/nix-js/runtime-ts/src/builtins/hash.ts +++ b/nix-js/runtime-ts/src/builtins/hash.ts @@ -1,10 +1,12 @@ import { forceStringNoCtx } from "../type-assert"; import type { NixValue } from "../types"; +import { realisePath } from "./io"; export const hashFile = (type: NixValue) => - (_p: NixValue): never => { + (p: NixValue): string => { const _ty = forceStringNoCtx(type); + const _pathStr = realisePath(p); throw new Error("Not implemented: hashFile"); }; diff --git a/nix-js/runtime-ts/src/builtins/io.ts b/nix-js/runtime-ts/src/builtins/io.ts index 1319ec5..7749bd5 100644 --- a/nix-js/runtime-ts/src/builtins/io.ts +++ b/nix-js/runtime-ts/src/builtins/io.ts @@ -1,6 +1,6 @@ import { getPathValue } from "../path"; import type { NixStringContext, StringWithContext } from "../string-context"; -import { addOpaqueContext, mkStringWithContext } from "../string-context"; +import { addOpaqueContext, decodeContextElem, mkStringWithContext } from "../string-context"; import { force } from "../thunk"; import { forceAttrs, @@ -14,32 +14,40 @@ import type { NixAttrs, NixPath, NixString, NixValue } from "../types"; import { CatchableError, IS_PATH, isNixPath } from "../types"; import { coerceToPath, coerceToString, StringCoercionMode } from "./conversion"; import { baseNameOf } from "./path"; -import { isAttrs, isPath } from "./type-check"; +import { isAttrs, isPath, isString } from "./type-check"; const importCache = new Map(); -export const importFunc = (path: NixValue): NixValue => { - const context: NixStringContext = new Set(); - const pathStr = coerceToPath(path, context); - - // FIXME: Context collected but not yet propagated to build system - // This means derivation dependencies from imported paths are not - // currently tracked. This will cause issues when: - // 1. Importing from derivation outputs: import "${drv}/file.nix" - // 2. Building packages that depend on imported configurations - if (context.size > 0) { - console.warn( - `[WARN] import: Path has string context which is not yet fully tracked. -Dependency tracking for imported derivations may be incomplete.`, - ); +const realiseContext = (context: NixStringContext): void => { + for (const encoded of context) { + const elem = decodeContextElem(encoded); + if (elem.type === "built") { + throw new Error( + `cannot build derivation '${elem.drvPath}' during evaluation because import-from-derivation is not supported`, + ); + } } +}; + +export const realisePath = (value: NixValue): string => { + const context: NixStringContext = new Set(); + const pathStr = coerceToPath(value, context); + + if (context.size > 0) { + realiseContext(context); + } + + return pathStr; +}; + +export const importFunc = (path: NixValue): NixValue => { + const pathStr = realisePath(path); const cached = importCache.get(pathStr); if (cached !== undefined) { return cached; } - // Call Rust op - returns JS code string const code = Deno.core.ops.op_import(pathStr); const result = Function(`return (${code})`)(); @@ -53,15 +61,7 @@ export const scopedImport = const scopeAttrs = forceAttrs(scope); const scopeKeys = Object.keys(scopeAttrs); - const context: NixStringContext = new Set(); - const pathStr = coerceToPath(path, context); - - if (context.size > 0) { - console.warn( - `[WARN] scopedImport: Path has string context which is not yet fully tracked. -Dependency tracking for imported derivations may be incomplete.`, - ); - } + const pathStr = realisePath(path); const code = Deno.core.ops.op_scoped_import(pathStr, scopeKeys); @@ -169,9 +169,10 @@ export const fetchTarball = (args: NixValue): NixString => { export const fetchGit = (args: NixValue): NixAttrs => { const forced = force(args); - if (typeof forced === "string" || isPath(forced)) { - const path = coerceToPath(forced); - const result: FetchGitResult = Deno.core.ops.op_fetch_git(path, null, null, false, false, false, null); + const disposedContext: NixStringContext = new Set(); + if (isString(forced) || isPath(forced)) { + const url = coerceToString(forced, StringCoercionMode.Base, false, disposedContext); + const result = Deno.core.ops.op_fetch_git(url, null, null, false, false, false, null); const outContext: NixStringContext = new Set(); addOpaqueContext(outContext, result.out_path); return { @@ -303,7 +304,7 @@ const autoDetectAndFetch = (attrs: NixAttrs): NixAttrs => { }; export const readDir = (path: NixValue): NixAttrs => { - const pathStr = coerceToPath(path); + const pathStr = realisePath(path); const entries: Record = Deno.core.ops.op_read_dir(pathStr); const result: NixAttrs = {}; for (const [name, type] of Object.entries(entries)) { @@ -313,18 +314,22 @@ export const readDir = (path: NixValue): NixAttrs => { }; export const readFile = (path: NixValue): string => { - const pathStr = coerceToPath(path); + const pathStr = realisePath(path); return Deno.core.ops.op_read_file(pathStr); }; export const readFileType = (path: NixValue): string => { - const pathStr = coerceToPath(path); + const pathStr = realisePath(path); return Deno.core.ops.op_read_file_type(pathStr); }; export const pathExists = (path: NixValue): boolean => { - const pathStr = coerceToPath(path); - return Deno.core.ops.op_path_exists(pathStr); + try { + const pathStr = realisePath(path); + return Deno.core.ops.op_path_exists(pathStr); + } catch { + return false; + } }; /** diff --git a/nix-js/runtime-ts/src/builtins/path.ts b/nix-js/runtime-ts/src/builtins/path.ts index 78ee51f..4d3d3bc 100644 --- a/nix-js/runtime-ts/src/builtins/path.ts +++ b/nix-js/runtime-ts/src/builtins/path.ts @@ -2,7 +2,7 @@ import { mkPath } from "../path"; import { mkStringWithContext, type NixStringContext } from "../string-context"; import { force } from "../thunk"; import type { NixPath, NixString, NixValue } from "../types"; -import { isNixPath, isStringWithContext } from "../types"; +import { isNixPath } from "../types"; import { coerceToPath, coerceToString, StringCoercionMode } from "./conversion"; /** @@ -86,21 +86,8 @@ export const dirOf = (s: NixValue): NixPath | NixString => { } // String input → string output - const strValue: NixString = coerceToString(s, StringCoercionMode.Base, false) as NixString; - - let pathStr: string; - let hasContext = false; - let originalContext: Set | undefined; - - if (typeof strValue === "string") { - pathStr = strValue; - } else if (isStringWithContext(strValue)) { - pathStr = strValue.value; - hasContext = strValue.context.size > 0; - originalContext = strValue.context; - } else { - pathStr = strValue as string; - } + const outContext: NixStringContext = new Set(); + const pathStr = coerceToString(s, StringCoercionMode.Base, false, outContext); const lastSlash = pathStr.lastIndexOf("/"); @@ -113,9 +100,8 @@ export const dirOf = (s: NixValue): NixPath | NixString => { const result = pathStr.slice(0, lastSlash); - // Preserve string context if present - if (hasContext && originalContext) { - return mkStringWithContext(result, originalContext); + if (outContext.size > 0) { + return mkStringWithContext(result, outContext); } return result;