fix: derivation & derivationStrict

This commit is contained in:
2026-01-25 00:48:53 +08:00
parent 51f7f4079b
commit 4d68fb26d9
2 changed files with 153 additions and 54 deletions

View File

@@ -1,9 +1,16 @@
import type { NixValue, NixAttrs } from "../types"; import type { NixValue, NixAttrs } from "../types";
import { forceStringValue, forceList } from "../type-assert"; import { forceStringValue, forceList } from "../type-assert";
import { force } from "../thunk"; import { force, createThunk } from "../thunk";
import { type DerivationData, type OutputInfo, generateAterm } from "../derivation-helpers"; import { type DerivationData, type OutputInfo, generateAterm } from "../derivation-helpers";
import { coerceToString, StringCoercionMode } from "./conversion"; import { coerceToString, StringCoercionMode } from "./conversion";
import { type NixStringContext, extractInputDrvsAndSrcs, isStringWithContext } from "../string-context"; import {
type NixStringContext,
extractInputDrvsAndSrcs,
isStringWithContext,
mkStringWithContext,
addDrvDeepContext,
addBuiltContext,
} from "../string-context";
import { nixValueToJson } from "../conversion"; import { nixValueToJson } from "../conversion";
import { isNixPath } from "../types"; import { isNixPath } from "../types";
@@ -88,6 +95,7 @@ const extractEnv = (
structuredAttrs: boolean, structuredAttrs: boolean,
ignoreNulls: boolean, ignoreNulls: boolean,
outContext: NixStringContext, outContext: NixStringContext,
drvName: string,
): Map<string, string> => { ): Map<string, string> => {
const specialAttrs = new Set([ const specialAttrs = new Set([
"name", "name",
@@ -113,6 +121,49 @@ const extractEnv = (
} }
jsonAttrs[key] = nixValueToJson(value, new Set(), outContext); jsonAttrs[key] = nixValueToJson(value, new Set(), outContext);
} }
if (key === "allowedReferences") {
console.warn(
`In a derivation named '${drvName}', 'structuredAttrs' disables the effect of ` +
`the derivation attribute 'allowedReferences'; use ` +
`'outputChecks.<output>.allowedReferences' instead`
);
}
if (key === "allowedRequisites") {
console.warn(
`In a derivation named '${drvName}', 'structuredAttrs' disables the effect of ` +
`the derivation attribute 'allowedRequisites'; use ` +
`'outputChecks.<output>.allowedRequisites' instead`
);
}
if (key === "disallowedReferences") {
console.warn(
`In a derivation named '${drvName}', 'structuredAttrs' disables the effect of ` +
`the derivation attribute 'disallowedReferences'; use ` +
`'outputChecks.<output>.disallowedReferences' instead`
);
}
if (key === "disallowedRequisites") {
console.warn(
`In a derivation named '${drvName}', 'structuredAttrs' disables the effect of ` +
`the derivation attribute 'disallowedRequisites'; use ` +
`'outputChecks.<output>.disallowedRequisites' instead`
);
}
if (key === "maxSize") {
console.warn(
`In a derivation named '${drvName}', 'structuredAttrs' disables the effect of ` +
`the derivation attribute 'maxSize'; use ` +
`'outputChecks.<output>.maxSize' instead`
);
}
if (key === "maxClosureSize") {
console.warn(
`In a derivation named '${drvName}', 'structuredAttrs' disables the effect of ` +
`the derivation attribute 'maxClosureSize'; use ` +
`'outputChecks.<output>.maxClosureSize' instead`
);
}
} }
env.set("__json", JSON.stringify(jsonAttrs)); env.set("__json", JSON.stringify(jsonAttrs));
} else { } else {
@@ -174,8 +225,16 @@ export const derivationStrict = (args: NixValue): NixAttrs => {
const ignoreNulls = "__ignoreNulls" in attrs ? force(attrs.__ignoreNulls) === true : false; const ignoreNulls = "__ignoreNulls" in attrs ? force(attrs.__ignoreNulls) === true : false;
if ("__contentAddressed" in attrs && force(attrs.__contentAddressed) === true) {
throw new Error("ca derivations are not supported");
}
if ("impure" in attrs && force(attrs.impure) === true) {
throw new Error("impure derivations are not supported");
}
const drvArgs = extractArgs(attrs, collectedContext); const drvArgs = extractArgs(attrs, collectedContext);
const env = extractEnv(attrs, structuredAttrs, ignoreNulls, collectedContext); const env = extractEnv(attrs, structuredAttrs, ignoreNulls, collectedContext, drvName);
env.set("name", drvName); env.set("name", drvName);
env.set("builder", builder); env.set("builder", builder);
@@ -277,37 +336,94 @@ export const derivationStrict = (args: NixValue): NixAttrs => {
drvPath = Deno.core.ops.op_make_store_path("text", finalDrvHash, `${drvName}.drv`); drvPath = Deno.core.ops.op_make_store_path("text", finalDrvHash, `${drvName}.drv`);
} }
const result: NixAttrs = { const result: NixAttrs = {};
type: "derivation",
drvPath, const drvPathContext = new Set<string>();
name: drvName, addDrvDeepContext(drvPathContext, drvPath);
builder, result.drvPath = mkStringWithContext(drvPath, drvPathContext);
system: platform,
};
for (const [outputName, outputInfo] of outputInfos.entries()) { for (const [outputName, outputInfo] of outputInfos.entries()) {
result[outputName] = outputInfo.path; const outputContext = new Set<string>();
} addBuiltContext(outputContext, drvPath, outputName);
result[outputName] = mkStringWithContext(outputInfo.path, outputContext);
if (outputInfos.has("out")) {
result.outPath = outputInfos.get("out")!.path;
}
if (drvArgs.length > 0) {
result.args = drvArgs;
}
if (!structuredAttrs) {
for (const [key, value] of env.entries()) {
if (!["name", "builder", "system", ...outputs].includes(key)) {
result[key] = value;
}
}
} }
return result; return result;
}; };
export const derivation = (args: NixValue): NixAttrs => { export const derivation = (args: NixValue): NixAttrs => {
return derivationStrict(args); const attrs = forceAttrs(args);
const strict = derivationStrict(args);
const outputs: string[] = extractOutputs(attrs);
const drvName = validateName(attrs);
const collectedContext: NixStringContext = new Set();
const builder = validateBuilder(attrs, collectedContext);
const platform = validateSystem(attrs);
const structuredAttrs = "__structuredAttrs" in attrs ? force(attrs.__structuredAttrs) === true : false;
const ignoreNulls = "__ignoreNulls" in attrs ? force(attrs.__ignoreNulls) === true : false;
const drvArgs = extractArgs(attrs, collectedContext);
const specialAttrs = new Set([
"name",
"builder",
"system",
"args",
"outputs",
"__structuredAttrs",
"__ignoreNulls",
"__contentAddressed",
"impure",
]);
const baseAttrs: NixAttrs = {
type: "derivation",
drvPath: strict.drvPath,
name: drvName,
builder,
system: platform,
}; };
if (drvArgs.length > 0) {
baseAttrs.args = drvArgs;
}
if (!structuredAttrs) {
for (const [key, value] of Object.entries(attrs)) {
if (!specialAttrs.has(key) && !outputs.includes(key)) {
const forcedValue = force(value);
if (!(ignoreNulls && forcedValue === null)) {
baseAttrs[key] = value;
}
}
}
}
const outputsList: NixAttrs[] = [];
for (const outputName of outputs) {
const outputObj: NixAttrs = {
...baseAttrs,
outPath: strict[outputName],
outputName,
};
outputsList.push(outputObj);
}
baseAttrs.drvAttrs = attrs;
for (const [i, outputName] of outputs.entries()) {
baseAttrs[outputName] = createThunk(() => outputsList[i], `output_${outputName}`);
}
baseAttrs.all = createThunk(() => outputsList, "all_outputs");
for (const outputObj of outputsList) {
outputObj.drvAttrs = attrs;
for (const [i, outputName] of outputs.entries()) {
outputObj[outputName] = createThunk(() => outputsList[i], `output_${outputName}`);
}
outputObj.all = createThunk(() => outputsList, "all_outputs");
}
return outputsList[0];
};

View File

@@ -143,9 +143,10 @@ fn derivation_strict() {
match result { match result {
Value::AttrSet(attrs) => { Value::AttrSet(attrs) => {
assert_eq!(attrs.get("type"), Some(&Value::String("derivation".into())));
assert!(attrs.contains_key("drvPath")); assert!(attrs.contains_key("drvPath"));
assert!(attrs.contains_key("outPath")); assert!(attrs.contains_key("out"));
assert!(!attrs.contains_key("type"));
assert!(!attrs.contains_key("outPath"));
} }
_ => panic!("Expected AttrSet"), _ => panic!("Expected AttrSet"),
} }
@@ -189,7 +190,7 @@ fn derivation_escaping_in_aterm() {
#[test] #[test]
fn multi_output_two_outputs() { fn multi_output_two_outputs() {
let result = eval( let drv = eval(
r#"derivation { r#"derivation {
name = "multi"; name = "multi";
builder = "/bin/sh"; builder = "/bin/sh";
@@ -198,39 +199,20 @@ fn multi_output_two_outputs() {
}"#, }"#,
); );
match result { match drv {
Value::AttrSet(attrs) => { Value::AttrSet(attrs) => {
assert!(attrs.contains_key("drvPath"));
assert!(attrs.contains_key("out")); assert!(attrs.contains_key("out"));
assert!(attrs.contains_key("dev")); assert!(attrs.contains_key("dev"));
assert!(attrs.contains_key("outPath")); assert!(attrs.contains_key("outPath"));
assert!(attrs.contains_key("drvPath"));
// Verify exact paths match CppNix
if let Some(Value::String(drv_path)) = attrs.get("drvPath") { if let Some(Value::String(drv_path)) = attrs.get("drvPath") {
assert_eq!( assert_eq!(
drv_path, drv_path,
"/nix/store/vmyjryfipkn9ss3ya23hk8p3m58l6dsl-multi.drv" "/nix/store/vmyjryfipkn9ss3ya23hk8p3m58l6dsl-multi.drv"
); );
} else { } else {
panic!("drvPath should be a string"); panic!("drvPath should be a string, got: {:?}", attrs.get("drvPath"));
}
if let Some(Value::String(out_path)) = attrs.get("out") {
assert_eq!(
out_path,
"/nix/store/a3d95yg9d215c54n0ybr4npmpnj29229-multi"
);
} else {
panic!("out should be a string");
}
if let Some(Value::String(dev_path)) = attrs.get("dev") {
assert_eq!(
dev_path,
"/nix/store/hq3b99lz71gwfq6x8lqwg14hf929q0d2-multi-dev"
);
} else {
panic!("dev should be a string");
} }
if let Some(Value::String(out_path)) = attrs.get("outPath") { if let Some(Value::String(out_path)) = attrs.get("outPath") {
@@ -238,7 +220,8 @@ fn multi_output_two_outputs() {
out_path, out_path,
"/nix/store/a3d95yg9d215c54n0ybr4npmpnj29229-multi" "/nix/store/a3d95yg9d215c54n0ybr4npmpnj29229-multi"
); );
assert_eq!(attrs.get("out"), Some(&Value::String(out_path.clone()))); } else {
panic!("outPath should be a string");
} }
} }
_ => panic!("Expected AttrSet"), _ => panic!("Expected AttrSet"),