feat: implement realisePath

This commit is contained in:
2026-02-15 16:50:20 +08:00
parent 2f2c690023
commit e357678d70
5 changed files with 55 additions and 68 deletions

View File

@@ -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;
}

View File

@@ -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 =

View File

@@ -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");
};

View File

@@ -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<string, NixValue>();
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<string, string> = 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;
}
};
/**

View File

@@ -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<string> | 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;