Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Python venv support #97

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
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
Loading