fix: preserve string context in builtins.{path,fetch*}
This commit is contained in:
@@ -11,13 +11,13 @@ import {
|
|||||||
forceStringNoCtx,
|
forceStringNoCtx,
|
||||||
forceStringValue,
|
forceStringValue,
|
||||||
} from "../type-assert";
|
} 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 { isNixPath, IS_PATH, CatchableError } from "../types";
|
||||||
import { force } from "../thunk";
|
import { force } from "../thunk";
|
||||||
import { coerceToPath, coerceToString, StringCoercionMode } from "./conversion";
|
import { coerceToPath, coerceToString, StringCoercionMode } from "./conversion";
|
||||||
import { getPathValue } from "../path";
|
import { getPathValue } from "../path";
|
||||||
import type { NixStringContext, StringWithContext } from "../string-context";
|
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 { isAttrs, isPath } from "./type-check";
|
||||||
import { baseNameOf } from "./path";
|
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 { url, hash, name, executable } = normalizeUrlInput(args);
|
||||||
const result: FetchUrlResult = Deno.core.ops.op_fetch_url(
|
const result: FetchUrlResult = Deno.core.ops.op_fetch_url(
|
||||||
url,
|
url,
|
||||||
@@ -159,13 +159,17 @@ export const fetchurl = (args: NixValue): string => {
|
|||||||
name ?? null,
|
name ?? null,
|
||||||
executable ?? false,
|
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 { url, name, sha256 } = normalizeTarballInput(args);
|
||||||
const result: FetchTarballResult = Deno.core.ops.op_fetch_tarball(url, name ?? null, sha256 ?? null);
|
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 => {
|
export const fetchGit = (args: NixValue): NixAttrs => {
|
||||||
@@ -173,8 +177,10 @@ export const fetchGit = (args: NixValue): NixAttrs => {
|
|||||||
if (typeof forced === "string" || isPath(forced)) {
|
if (typeof forced === "string" || isPath(forced)) {
|
||||||
const path = coerceToPath(forced);
|
const path = coerceToPath(forced);
|
||||||
const result: FetchGitResult = Deno.core.ops.op_fetch_git(path, null, null, false, false, false, null);
|
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 {
|
return {
|
||||||
outPath: result.out_path,
|
outPath: mkStringWithContext(result.out_path, outContext),
|
||||||
rev: result.rev,
|
rev: result.rev,
|
||||||
shortRev: result.short_rev,
|
shortRev: result.short_rev,
|
||||||
revCount: BigInt(result.rev_count),
|
revCount: BigInt(result.rev_count),
|
||||||
@@ -203,8 +209,10 @@ export const fetchGit = (args: NixValue): NixAttrs => {
|
|||||||
name,
|
name,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const outContext: NixStringContext = new Set();
|
||||||
|
addOpaqueContext(outContext, result.out_path);
|
||||||
return {
|
return {
|
||||||
outPath: result.out_path,
|
outPath: mkStringWithContext(result.out_path, outContext),
|
||||||
rev: result.rev,
|
rev: result.rev,
|
||||||
shortRev: result.short_rev,
|
shortRev: result.short_rev,
|
||||||
revCount: BigInt(result.rev_count),
|
revCount: BigInt(result.rev_count),
|
||||||
@@ -338,10 +346,9 @@ export const pathExists = (path: NixValue): boolean => {
|
|||||||
*
|
*
|
||||||
* Returns: Store path string
|
* Returns: Store path string
|
||||||
*/
|
*/
|
||||||
export const path = (args: NixValue): string => {
|
export const path = (args: NixValue): NixString => {
|
||||||
const attrs = forceAttrs(args);
|
const attrs = forceAttrs(args);
|
||||||
|
|
||||||
// Required: path parameter
|
|
||||||
if (!("path" in attrs)) {
|
if (!("path" in attrs)) {
|
||||||
throw new TypeError("builtins.path: 'path' attribute is required");
|
throw new TypeError("builtins.path: 'path' attribute is required");
|
||||||
}
|
}
|
||||||
@@ -349,23 +356,18 @@ export const path = (args: NixValue): string => {
|
|||||||
const pathValue = force(attrs.path);
|
const pathValue = force(attrs.path);
|
||||||
let pathStr: string;
|
let pathStr: string;
|
||||||
|
|
||||||
// Accept both Path values and strings
|
|
||||||
if (isNixPath(pathValue)) {
|
if (isNixPath(pathValue)) {
|
||||||
pathStr = getPathValue(pathValue);
|
pathStr = getPathValue(pathValue);
|
||||||
} else {
|
} else {
|
||||||
pathStr = forceStringValue(pathValue);
|
pathStr = forceStringValue(pathValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional: name parameter (defaults to basename in Rust)
|
|
||||||
const name = "name" in attrs ? forceStringValue(attrs.name) : null;
|
const name = "name" in attrs ? forceStringValue(attrs.name) : null;
|
||||||
|
|
||||||
// Optional: recursive parameter (default: true)
|
|
||||||
const recursive = "recursive" in attrs ? forceBool(attrs.recursive) : true;
|
const recursive = "recursive" in attrs ? forceBool(attrs.recursive) : true;
|
||||||
|
|
||||||
// Optional: sha256 parameter
|
|
||||||
const sha256 = "sha256" in attrs ? forceStringValue(attrs.sha256) : null;
|
const sha256 = "sha256" in attrs ? forceStringValue(attrs.sha256) : null;
|
||||||
|
|
||||||
// Handle filter parameter
|
let storePath: string;
|
||||||
|
|
||||||
if ("filter" in attrs) {
|
if ("filter" in attrs) {
|
||||||
const filterFn = forceFunction(attrs.filter);
|
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,
|
pathStr,
|
||||||
name,
|
name,
|
||||||
recursive,
|
recursive,
|
||||||
sha256,
|
sha256,
|
||||||
includePaths,
|
includePaths,
|
||||||
);
|
);
|
||||||
return storePath;
|
} else {
|
||||||
|
storePath = Deno.core.ops.op_add_path(pathStr, name, recursive, sha256);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call Rust op to add path to store
|
const context: NixStringContext = new Set();
|
||||||
const storePath: string = Deno.core.ops.op_add_path(pathStr, name, recursive, sha256);
|
addOpaqueContext(context, storePath);
|
||||||
|
return mkStringWithContext(storePath, context);
|
||||||
return storePath;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toFile =
|
export const toFile =
|
||||||
|
|||||||
@@ -489,3 +489,76 @@ fn split_no_match_preserves_context() {
|
|||||||
);
|
);
|
||||||
assert_eq!(result, Value::Bool(true));
|
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