diff --git a/nix-js/runtime-ts/src/builtins/derivation.ts b/nix-js/runtime-ts/src/builtins/derivation.ts index 58e05db..c15e012 100644 --- a/nix-js/runtime-ts/src/builtins/derivation.ts +++ b/nix-js/runtime-ts/src/builtins/derivation.ts @@ -1,9 +1,16 @@ import type { NixValue, NixAttrs } from "../types"; 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 { 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 { isNixPath } from "../types"; @@ -88,6 +95,7 @@ const extractEnv = ( structuredAttrs: boolean, ignoreNulls: boolean, outContext: NixStringContext, + drvName: string, ): Map => { const specialAttrs = new Set([ "name", @@ -113,6 +121,49 @@ const extractEnv = ( } 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..allowedReferences' instead` + ); + } + if (key === "allowedRequisites") { + console.warn( + `In a derivation named '${drvName}', 'structuredAttrs' disables the effect of ` + + `the derivation attribute 'allowedRequisites'; use ` + + `'outputChecks..allowedRequisites' instead` + ); + } + if (key === "disallowedReferences") { + console.warn( + `In a derivation named '${drvName}', 'structuredAttrs' disables the effect of ` + + `the derivation attribute 'disallowedReferences'; use ` + + `'outputChecks..disallowedReferences' instead` + ); + } + if (key === "disallowedRequisites") { + console.warn( + `In a derivation named '${drvName}', 'structuredAttrs' disables the effect of ` + + `the derivation attribute 'disallowedRequisites'; use ` + + `'outputChecks..disallowedRequisites' instead` + ); + } + if (key === "maxSize") { + console.warn( + `In a derivation named '${drvName}', 'structuredAttrs' disables the effect of ` + + `the derivation attribute 'maxSize'; use ` + + `'outputChecks..maxSize' instead` + ); + } + if (key === "maxClosureSize") { + console.warn( + `In a derivation named '${drvName}', 'structuredAttrs' disables the effect of ` + + `the derivation attribute 'maxClosureSize'; use ` + + `'outputChecks..maxClosureSize' instead` + ); + } } env.set("__json", JSON.stringify(jsonAttrs)); } else { @@ -174,8 +225,16 @@ export const derivationStrict = (args: NixValue): NixAttrs => { 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 env = extractEnv(attrs, structuredAttrs, ignoreNulls, collectedContext); + const env = extractEnv(attrs, structuredAttrs, ignoreNulls, collectedContext, drvName); env.set("name", drvName); 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`); } - const result: NixAttrs = { - type: "derivation", - drvPath, - name: drvName, - builder, - system: platform, - }; + const result: NixAttrs = {}; + + const drvPathContext = new Set(); + addDrvDeepContext(drvPathContext, drvPath); + result.drvPath = mkStringWithContext(drvPath, drvPathContext); for (const [outputName, outputInfo] of outputInfos.entries()) { - result[outputName] = outputInfo.path; - } - - 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; - } - } + const outputContext = new Set(); + addBuiltContext(outputContext, drvPath, outputName); + result[outputName] = mkStringWithContext(outputInfo.path, outputContext); } return result; }; 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]; }; + diff --git a/nix-js/tests/derivation.rs b/nix-js/tests/derivation.rs index 34cefb4..c489a42 100644 --- a/nix-js/tests/derivation.rs +++ b/nix-js/tests/derivation.rs @@ -143,9 +143,10 @@ fn derivation_strict() { match result { Value::AttrSet(attrs) => { - assert_eq!(attrs.get("type"), Some(&Value::String("derivation".into()))); 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"), } @@ -189,7 +190,7 @@ fn derivation_escaping_in_aterm() { #[test] fn multi_output_two_outputs() { - let result = eval( + let drv = eval( r#"derivation { name = "multi"; builder = "/bin/sh"; @@ -198,39 +199,20 @@ fn multi_output_two_outputs() { }"#, ); - match result { + match drv { Value::AttrSet(attrs) => { + assert!(attrs.contains_key("drvPath")); assert!(attrs.contains_key("out")); assert!(attrs.contains_key("dev")); 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") { assert_eq!( drv_path, "/nix/store/vmyjryfipkn9ss3ya23hk8p3m58l6dsl-multi.drv" ); } else { - panic!("drvPath should be a string"); - } - - 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"); + panic!("drvPath should be a string, got: {:?}", attrs.get("drvPath")); } if let Some(Value::String(out_path)) = attrs.get("outPath") { @@ -238,7 +220,8 @@ fn multi_output_two_outputs() { out_path, "/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"),