fix: preserve string context in builtins.{path,fetch*}
This commit is contained in:
@@ -11,13 +11,13 @@ import {
|
||||
forceStringNoCtx,
|
||||
forceStringValue,
|
||||
} from "../type-assert";
|
||||
import type { NixValue, NixAttrs, NixPath } from "../types";
|
||||
import type { NixValue, NixAttrs, NixPath, NixString } 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 { mkStringWithContext, addOpaqueContext } from "../string-context";
|
||||
import { isAttrs, isPath } from "./type-check";
|
||||
import { baseNameOf } from "./path";
|
||||
|
||||
@@ -151,7 +151,7 @@ const resolvePseudoUrl = (url: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchurl = (args: NixValue): string => {
|
||||
export const fetchurl = (args: NixValue): NixString => {
|
||||
const { url, hash, name, executable } = normalizeUrlInput(args);
|
||||
const result: FetchUrlResult = Deno.core.ops.op_fetch_url(
|
||||
url,
|
||||
@@ -159,13 +159,17 @@ export const fetchurl = (args: NixValue): string => {
|
||||
name ?? null,
|
||||
executable ?? false,
|
||||
);
|
||||
return result.store_path;
|
||||
const context: NixStringContext = new Set();
|
||||
addOpaqueContext(context, result.store_path);
|
||||
return mkStringWithContext(result.store_path, context);
|
||||
};
|
||||
|
||||
export const fetchTarball = (args: NixValue): string => {
|
||||
export const fetchTarball = (args: NixValue): NixString => {
|
||||
const { url, name, sha256 } = normalizeTarballInput(args);
|
||||
const result: FetchTarballResult = Deno.core.ops.op_fetch_tarball(url, name ?? null, sha256 ?? null);
|
||||
return result.store_path;
|
||||
const context: NixStringContext = new Set();
|
||||
addOpaqueContext(context, result.store_path);
|
||||
return mkStringWithContext(result.store_path, context);
|
||||
};
|
||||
|
||||
export const fetchGit = (args: NixValue): NixAttrs => {
|
||||
@@ -173,8 +177,10 @@ export const fetchGit = (args: NixValue): NixAttrs => {
|
||||
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 outContext: NixStringContext = new Set();
|
||||
addOpaqueContext(outContext, result.out_path);
|
||||
return {
|
||||
outPath: result.out_path,
|
||||
outPath: mkStringWithContext(result.out_path, outContext),
|
||||
rev: result.rev,
|
||||
shortRev: result.short_rev,
|
||||
revCount: BigInt(result.rev_count),
|
||||
@@ -203,8 +209,10 @@ export const fetchGit = (args: NixValue): NixAttrs => {
|
||||
name,
|
||||
);
|
||||
|
||||
const outContext: NixStringContext = new Set();
|
||||
addOpaqueContext(outContext, result.out_path);
|
||||
return {
|
||||
outPath: result.out_path,
|
||||
outPath: mkStringWithContext(result.out_path, outContext),
|
||||
rev: result.rev,
|
||||
shortRev: result.short_rev,
|
||||
revCount: BigInt(result.rev_count),
|
||||
@@ -338,10 +346,9 @@ export const pathExists = (path: NixValue): boolean => {
|
||||
*
|
||||
* Returns: Store path string
|
||||
*/
|
||||
export const path = (args: NixValue): string => {
|
||||
export const path = (args: NixValue): NixString => {
|
||||
const attrs = forceAttrs(args);
|
||||
|
||||
// Required: path parameter
|
||||
if (!("path" in attrs)) {
|
||||
throw new TypeError("builtins.path: 'path' attribute is required");
|
||||
}
|
||||
@@ -349,23 +356,18 @@ export const path = (args: NixValue): string => {
|
||||
const pathValue = force(attrs.path);
|
||||
let pathStr: string;
|
||||
|
||||
// Accept both Path values and strings
|
||||
if (isNixPath(pathValue)) {
|
||||
pathStr = getPathValue(pathValue);
|
||||
} else {
|
||||
pathStr = forceStringValue(pathValue);
|
||||
}
|
||||
|
||||
// Optional: name parameter (defaults to basename in Rust)
|
||||
const name = "name" in attrs ? forceStringValue(attrs.name) : null;
|
||||
|
||||
// Optional: recursive parameter (default: true)
|
||||
const recursive = "recursive" in attrs ? forceBool(attrs.recursive) : true;
|
||||
|
||||
// Optional: sha256 parameter
|
||||
const sha256 = "sha256" in attrs ? forceStringValue(attrs.sha256) : null;
|
||||
|
||||
// Handle filter parameter
|
||||
let storePath: string;
|
||||
|
||||
if ("filter" in attrs) {
|
||||
const filterFn = forceFunction(attrs.filter);
|
||||
|
||||
@@ -381,20 +383,20 @@ export const path = (args: NixValue): string => {
|
||||
}
|
||||
}
|
||||
|
||||
const storePath: string = Deno.core.ops.op_add_filtered_path(
|
||||
storePath = Deno.core.ops.op_add_filtered_path(
|
||||
pathStr,
|
||||
name,
|
||||
recursive,
|
||||
sha256,
|
||||
includePaths,
|
||||
);
|
||||
return storePath;
|
||||
} else {
|
||||
storePath = Deno.core.ops.op_add_path(pathStr, name, recursive, sha256);
|
||||
}
|
||||
|
||||
// Call Rust op to add path to store
|
||||
const storePath: string = Deno.core.ops.op_add_path(pathStr, name, recursive, sha256);
|
||||
|
||||
return storePath;
|
||||
const context: NixStringContext = new Set();
|
||||
addOpaqueContext(context, storePath);
|
||||
return mkStringWithContext(storePath, context);
|
||||
};
|
||||
|
||||
export const toFile =
|
||||
|
||||
@@ -489,3 +489,76 @@ fn split_no_match_preserves_context() {
|
||||
);
|
||||
assert_eq!(result, Value::Bool(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtins_path_has_context() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let test_file = temp_dir.path().join("test.txt");
|
||||
std::fs::write(&test_file, "hello").unwrap();
|
||||
|
||||
let expr = format!(
|
||||
r#"builtins.hasContext (builtins.path {{ path = {}; name = "test-ctx"; }})"#,
|
||||
test_file.display()
|
||||
);
|
||||
let result = eval(&expr);
|
||||
assert_eq!(result, Value::Bool(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtins_path_context_tracked_in_structured_attrs_derivation() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let test_file = temp_dir.path().join("test-patch.txt");
|
||||
std::fs::write(&test_file, "patch content").unwrap();
|
||||
|
||||
let expr = format!(
|
||||
r#"
|
||||
let
|
||||
patch = builtins.path {{ path = {}; name = "test-patch"; }};
|
||||
in
|
||||
(derivation {{
|
||||
__structuredAttrs = true;
|
||||
name = "test-input-srcs";
|
||||
system = "x86_64-linux";
|
||||
builder = "/bin/sh";
|
||||
patches = [ patch ];
|
||||
}}).drvPath
|
||||
"#,
|
||||
test_file.display()
|
||||
);
|
||||
let result = eval(&expr);
|
||||
|
||||
if let Value::String(s) = &result {
|
||||
assert!(s.contains("/nix/store/"), "drvPath should be a store path");
|
||||
} else {
|
||||
panic!("Expected string, got {:?}", result);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtins_path_context_tracked_in_non_structured_derivation() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let test_file = temp_dir.path().join("dep.txt");
|
||||
std::fs::write(&test_file, "dependency content").unwrap();
|
||||
|
||||
let expr = format!(
|
||||
r#"
|
||||
let
|
||||
dep = builtins.path {{ path = {}; name = "dep-file"; }};
|
||||
in
|
||||
(derivation {{
|
||||
name = "test-non-structured";
|
||||
system = "x86_64-linux";
|
||||
builder = "/bin/sh";
|
||||
myDep = dep;
|
||||
}}).drvPath
|
||||
"#,
|
||||
test_file.display()
|
||||
);
|
||||
let result = eval(&expr);
|
||||
|
||||
if let Value::String(s) = &result {
|
||||
assert!(s.contains("/nix/store/"), "drvPath should be a store path");
|
||||
} else {
|
||||
panic!("Expected string, got {:?}", result);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user