Skip to content
Open
42 changes: 21 additions & 21 deletions .ghjk/lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,29 @@
"portRef": "[email protected]",
"specifiedVersion": false
},
"bciqj4p5hoqweghbuvz52rupja7sqze34z63dd62nz632c5zxikv6ezy": {
"version": "1.34",
"buildDepConfigs": {},
"portRef": "[email protected]",
"specifiedVersion": false
},
"bciqe6fwheayositrdk7rkr2ngdr4wizldakex23tgivss7w6z7g3q3y": {
"version": "v1.4.8,",
"buildDepConfigs": {},
"portRef": "[email protected]",
"specifiedVersion": false
},
"bciqfvlwwndlfuqibybkgee3fgt7cst5ltpztmm3by6hib5veial5spy": {
"version": "v1.44.2",
"buildDepConfigs": {},
"portRef": "[email protected]",
"specifiedVersion": true
},
"bciqao2s3r3r33ruox4qknfrxqrmemuccxn64dze2ylojrzp2bwvt4ji": {
"version": "3.7.1",
"version": "3.8.0",
"buildDepConfigs": {
"cpy_bs_ghrel": {
"version": "3.12.4",
"version": "3.12.5",
"buildDepConfigs": {
"tar_aa": {
"version": "1.34",
Expand All @@ -45,7 +63,7 @@
"specifiedVersion": false
},
"bciqij3g6mmbjn4a6ps4eipcy2fmw2zumgv5a3gbxycthroffihwquoi": {
"version": "3.12.4",
"version": "3.12.5",
"buildDepConfigs": {
"tar_aa": {
"version": "1.34",
Expand All @@ -62,24 +80,6 @@
},
"portRef": "[email protected]",
"specifiedVersion": false
},
"bciqj4p5hoqweghbuvz52rupja7sqze34z63dd62nz632c5zxikv6ezy": {
"version": "1.34",
"buildDepConfigs": {},
"portRef": "[email protected]",
"specifiedVersion": false
},
"bciqe6fwheayositrdk7rkr2ngdr4wizldakex23tgivss7w6z7g3q3y": {
"version": "v1.4.8,",
"buildDepConfigs": {},
"portRef": "[email protected]",
"specifiedVersion": false
},
"bciqfvlwwndlfuqibybkgee3fgt7cst5ltpztmm3by6hib5veial5spy": {
"version": "v1.44.2",
"buildDepConfigs": {},
"portRef": "[email protected]",
"specifiedVersion": true
}
}
},
Expand Down
1 change: 1 addition & 0 deletions examples/kitchen/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.venv/
7 changes: 6 additions & 1 deletion examples/kitchen/ghjk.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { stdDeps } from "../../files/mod.ts";
import { file } from "../../mod.ts";
import * as ports from "../../ports/mod.ts";
import { pyEnv } from "../../std/py.ts";

const ghjk = file({
// configre an empty env so that no ports are avail by default in our workdir
Expand Down Expand Up @@ -106,7 +107,8 @@ env("python")
ports.cpy_bs({ version: "3.8.18", releaseTag: "20240224" }),
ports.tar(),
ports.zstd(),
);
)
.mixin(pyEnv());

env("dev")
// we can inherit from many envs
Expand All @@ -120,3 +122,6 @@ env("dev")
workingDir: "..",
fn: ($) => $`ls`,
}));

env("venv")
.inherit(["python"]);
179 changes: 179 additions & 0 deletions files/cookbook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import {
EnvFinalizer,
FinalizedEnv,
InlineTaskHookProvision,
objectHashSafe,
TaskDefTyped,
} from "./mod.ts";
import {
EnvRecipe,
EnvsModuleConfig,
WellKnownProvision,
} from "../modules/envs/types.ts";
import { InstallSetRefProvision, unwrapZodRes } from "../port.ts";
import { InstallSet, MergedEnvs } from "./merged_envs.ts";
import envsValidators from "../modules/envs/types.ts";
import getLogger from "../utils/logger.ts";

export type Final = ReturnType<EnvFinalizer> & {
envBaseResolved: null | string[];
};

const logger = getLogger(import.meta);

interface MergedEntries {
vars: Record<string, string>;
dynVars: Record<string, string>;
}

export class Cookbook {
#moduleConfig: EnvsModuleConfig;

constructor(
public installSets: Map<string, InstallSet>,
public finalizedEnvs: Record<string, FinalizedEnv>,
public tasks: Map<string, TaskDefTyped>,
defaultEnv: string,
) {
this.#moduleConfig = {
envs: {},
defaultEnv,
envsNamed: {},
};
}

public registerEnv(final: Final, merged: MergedEnvs) {
const recipe = new RecipeBuilder(this, merged).build();

const installSetId = this.#getInstallSetId(final, merged.installSet);
if (installSetId) {
const prov: InstallSetRefProvision = {
ty: "ghjk.ports.InstallSetRef",
setId: installSetId,
};
recipe.provides.push(prov);
}

const hash = objectHashSafe(recipe);
this.finalizedEnvs[final.key] = {
installSetId,
finalized: final,
merged,
envHash: hash,
};

logger.debug("registering env", { key: final.key, name: final.name, hash });
this.#moduleConfig.envs[hash] = recipe;
if (final.name) {
this.#moduleConfig.envsNamed[final.name] = hash;
}
}

get moduleConfig() {
return this.#moduleConfig;
}

#getInstallSetId(final: Final, baseSet: InstallSet): string | undefined {
const installSet = this.installSets.get(final.installSetId);
if (installSet) {
installSet.installs = installSet.installs.union(
baseSet.installs,
);
for (
const [key, val] of Object.entries(
baseSet.allowedBuildDeps,
)
) {
// prefer the port dep config of the child over any
// similar deps in the base
if (!installSet.allowedBuildDeps[key]) {
installSet.allowedBuildDeps[key] = val;
}
}
return final.installSetId;
} // if there's no install set found under the id
else {
// implies that the env has not ports explicitly configured
if (final.envBaseResolved) {
// has a singluar parent
if (final.envBaseResolved.length == 1) {
return this.finalizedEnvs[final.envBaseResolved[0]]
.installSetId;
} else {
this.installSets.set(
final.installSetId,
baseSet,
);
return final.installSetId;
}
}
}
}
}

class RecipeBuilder {
constructor(
private book: Cookbook,
private compactEnv: MergedEnvs,
) {}

build(): EnvRecipe {
return {
desc: this.compactEnv.desc,
provides: [
...Object.entries(this.compactEnv.vars).map(([key, val]) => {
const prov: WellKnownProvision = { ty: "posix.envVar", key, val };
return prov;
}),
...Object.entries(this.compactEnv.dynVars).map(([key, val]) => {
const prov = { ty: "posix.envVarDyn", key, taskKey: val };
return unwrapZodRes(
envsValidators.envVarDynProvision.safeParse(prov),
prov,
);
}),
...this.compactEnv.posixDirs,
...this.compactEnv.dynamicPosixDirs,
// env hooks
...this.#getHooks(),
],
};
}

#getHooks(): InlineTaskHookProvision[] {
return [
...this.compactEnv.onEnterHookTasks.map(
(key) => [key, "hook.onEnter.ghjkTask"] as const,
),
...this.compactEnv.onExitHookTasks.map(
(key) => [key, "hook.onExit.ghjkTask"] as const,
),
].map(([taskKey, ty]) => {
const task = this.book.tasks.get(taskKey);
if (!task) {
throw new Error("unable to find task for onEnterHook", {
cause: {
env: this.compactEnv.name,
taskKey,
},
});
}
if (task.ty == "denoFile@v1") {
const prov: InlineTaskHookProvision = {
ty,
taskKey,
};
return prov;
}
throw new Error(
`unsupported task type "${task.ty}" used for environment hook`,
{
cause: {
taskKey,
task,
},
},
);
});
}
}
Loading