Skip to content

Commit

Permalink
[std] Add more functions for creating runnables (#115)
Browse files Browse the repository at this point in the history
* [Breaking] Refactor `std.ProcessTemplate` symbol types

* Add new `std.withRunnable()` function

* Refactor `std.bashRunnable()` to use `std.withRunnable()`

* Add new `std.addRunnable()` as a more general version of `std.withRunnable()`

* Fix handling of relative paths in `std.addRunnable()`
  • Loading branch information
kylewlacy authored Oct 5, 2024
1 parent e70bd2a commit 35e3bf3
Show file tree
Hide file tree
Showing 4 changed files with 318 additions and 149 deletions.
88 changes: 55 additions & 33 deletions packages/std/core/recipes/process.bri
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Awaitable, mixin } from "../utils.bri";
import { Awaitable, mixin, unreachable } from "../utils.bri";
import * as runtime from "../runtime.bri";
import type { File } from "./file.bri";
import { type Directory, directory } from "./directory.bri";
Expand Down Expand Up @@ -245,19 +245,34 @@ export function tpl(
// This is a branded type for one of the symbol type of process template
// components. Note that we avoid true symbol/branded types so the types can
// be structurally equivalent across different versions of `std`.
type ProcessTemplateSymbol<K extends string> = {
componentType: K;
type ProcessTemplateSymbol<
K extends ProcessTemplateSymbolKind = ProcessTemplateSymbolKind,
> = {
componentType: "symbol";
symbol: K;
} & {
[key in K]: never;
};
[key in K]?: never;
} & { __processTemplateSymbol: never };

const PROCESS_TEMPLATE_SYMBOL_KINDS = [
"outputPath",
"resourceDir",
"inputResourceDirs",
"homeDir",
"workDir",
"tempDir",
] as const;

type ProcessTemplateSymbolKind = (typeof PROCESS_TEMPLATE_SYMBOL_KINDS)[number];

/**
* Expands to the path where the process should write its output. Equivalent
* to the default environment variable `$BRIOCHE_OUTPUT`.
*/
// eslint-disable-next-line
export const outputPath = {
componentType: "outputPath",
componentType: "symbol",
symbol: "outputPath",
} as OutputPath;
type OutputPath = ProcessTemplateSymbol<"outputPath">;

Expand All @@ -268,7 +283,8 @@ type OutputPath = ProcessTemplateSymbol<"outputPath">;
*/
// eslint-disable-next-line
export const resourceDir = {
componentType: "resourceDir",
componentType: "symbol",
symbol: "resourceDir",
} as ResourceDir;
type ResourceDir = ProcessTemplateSymbol<"resourceDir">;

Expand All @@ -279,7 +295,8 @@ type ResourceDir = ProcessTemplateSymbol<"resourceDir">;
*/
// eslint-disable-next-line
export const inputResourceDirs = {
componentType: "inputResourceDirs",
componentType: "symbol",
symbol: "inputResourceDirs",
} as InputResourceDirs;
type InputResourceDirs = ProcessTemplateSymbol<"inputResourceDirs">;

Expand All @@ -289,7 +306,8 @@ type InputResourceDirs = ProcessTemplateSymbol<"inputResourceDirs">;
*/
// eslint-disable-next-line
export const homeDir = {
componentType: "homeDir",
componentType: "symbol",
symbol: "homeDir",
} as HomeDir;
type HomeDir = ProcessTemplateSymbol<"homeDir">;

Expand All @@ -299,7 +317,8 @@ type HomeDir = ProcessTemplateSymbol<"homeDir">;
*/
// eslint-disable-next-line
export const workDir = {
componentType: "workDir",
componentType: "symbol",
symbol: "workDir",
} as WorkDir;
type WorkDir = ProcessTemplateSymbol<"workDir">;

Expand All @@ -309,19 +328,22 @@ type WorkDir = ProcessTemplateSymbol<"workDir">;
*/
// eslint-disable-next-line
export const tempDir = {
componentType: "tempDir",
componentType: "symbol",
symbol: "tempDir",
} as TempDir;
type TempDir = ProcessTemplateSymbol<"tempDir">;

function isProcessTemplateSymbol<K extends string>(
function isProcessTemplateSymbol(
value: unknown,
componentType: K,
): value is ProcessTemplateSymbol<K> {
): value is ProcessTemplateSymbol {
return (
typeof value === "object" &&
value != null &&
"componentType" in value &&
value["componentType"] === componentType
value["componentType"] === "symbol" &&
"symbol" in value &&
typeof value.symbol === "string" &&
(PROCESS_TEMPLATE_SYMBOL_KINDS as readonly string[]).includes(value.symbol)
);
}

Expand All @@ -331,12 +353,7 @@ export type ProcessTemplateComponent =
| string
| ProcessTemplate
| Recipe
| typeof outputPath
| typeof resourceDir
| typeof inputResourceDirs
| typeof homeDir
| typeof workDir
| typeof tempDir
| ProcessTemplateSymbol
| undefined;

export class ProcessTemplate {
Expand All @@ -357,18 +374,23 @@ export class ProcessTemplate {
return [];
} else if (typeof component === "string") {
return [{ type: "literal", value: runtime.bstring(component) }];
} else if (isProcessTemplateSymbol(component, "outputPath")) {
return [{ type: "output_path" }];
} else if (isProcessTemplateSymbol(component, "resourceDir")) {
return [{ type: "resource_dir" }];
} else if (isProcessTemplateSymbol(component, "inputResourceDirs")) {
return [{ type: "input_resource_dirs" }];
} else if (isProcessTemplateSymbol(component, "homeDir")) {
return [{ type: "home_dir" }];
} else if (isProcessTemplateSymbol(component, "workDir")) {
return [{ type: "work_dir" }];
} else if (isProcessTemplateSymbol(component, "tempDir")) {
return [{ type: "temp_dir" }];
} else if (isProcessTemplateSymbol(component)) {
switch (component.symbol) {
case "outputPath":
return [{ type: "output_path" }];
case "resourceDir":
return [{ type: "resource_dir" }];
case "inputResourceDirs":
return [{ type: "input_resource_dirs" }];
case "homeDir":
return [{ type: "home_dir" }];
case "workDir":
return [{ type: "work_dir" }];
case "tempDir":
return [{ type: "temp_dir" }];
default:
return unreachable(component.symbol);
}
} else if (isProcessTemplateInstance(component)) {
const serialized = await component.briocheSerialize();
return serialized.components;
Expand Down
122 changes: 6 additions & 116 deletions packages/std/extra/bash_runnable.bri
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import * as std from "/core";
import { tools } from "/toolchain";
import {
type RunnableTemplate,
makeRunnableExecutable,
} from "/runnable_tools.bri";
import { withRunnable, type RunnableTemplateValue } from "./runnable.bri";

export type BashRunnable = std.Recipe<std.Directory> & BashRunnableUtils;

Expand Down Expand Up @@ -75,61 +72,13 @@ interface BashRunnableOptions {
}

function makeBashRunnable(options: BashRunnableOptions): BashRunnable {
let recipe = options.root;
let n = 0;
let command: RunnableTemplate = { components: [] };
[command, recipe, n] = buildTemplate([tools().get("bin/bash")], recipe, n);

const argTemplates: RunnableTemplateValue = [
"-e",
"-u",
"-o",
"pipefail",
"-c",
options.script,
"--",
];
const args: RunnableTemplate[] = [];
for (const arg of argTemplates) {
let argTemplate: RunnableTemplate;
[argTemplate, recipe, n] = buildTemplate(arg, recipe, n);
args.push(argTemplate);
}

const env: Record<string, RunnableTemplate> = {};
for (const [key, value] of Object.entries(options.env)) {
let valueTemplate: RunnableTemplate;
[valueTemplate, recipe, n] = buildTemplate(value, recipe, n);
env[key] = valueTemplate;
}

const path = env["PATH"] ?? { components: [] };
for (const dep of options.dependencies) {
let depTemplate: RunnableTemplate;
[depTemplate, recipe, n] = buildTemplate([dep, "/bin"], recipe, n);

if (path.components.length > 0) {
path.components.push(
{ type: "literal", value: std.bstring(":") },
...depTemplate.components,
);
} else {
path.components.push(...depTemplate.components);
}
}

if (path.components.length > 0) {
env["PATH"] = path;
}

const runnable = makeRunnableExecutable({
command,
args,
env,
const recipe = withRunnable(options.root, {
command: tools().get("bin/bash"),
args: ["-e", "-u", "-o", "pipefail", "-c", options.script, "--"],
env: options.env,
dependencies: options.dependencies,
});

recipe = recipe.insert("brioche-run", runnable);

return std.mixin(recipe, {
env(values: Record<string, RunnableTemplateValue>): BashRunnable {
return makeBashRunnable({
Expand All @@ -155,62 +104,3 @@ function makeBashRunnable(options: BashRunnableOptions): BashRunnable {
},
});
}

type RunnableTemplateValue =
| string
| undefined
| { relativePath: string }
| std.AsyncRecipe
| RunnableTemplateValue[];

function buildTemplate(
template: RunnableTemplateValue,
recipe: std.AsyncRecipe<std.Directory>,
n: number,
): [RunnableTemplate, std.Recipe<std.Directory>, number] {
let recipeValue = std.recipe(recipe);

if (template == null || template === "") {
return [{ components: [] }, recipeValue, n];
} else if (typeof template === "string") {
return [
{ components: [{ type: "literal", value: std.bstring(template) }] },
recipeValue,
n,
];
} else if (Array.isArray(template)) {
const resultComponents = [];
for (const component of template) {
let result: RunnableTemplate;
[result, recipeValue, n] = buildTemplate(component, recipeValue, n);

resultComponents.push(...result.components);
}

return [{ components: resultComponents }, recipeValue, n];
} else if ("relativePath" in template) {
return [
{
components: [
{ type: "relative_path", path: std.bstring(template.relativePath) },
],
},
recipeValue,
n,
];
} else {
recipeValue = recipeValue.insert(`brioche-run.d/recipe-${n}`, template);
return [
{
components: [
{
type: "relative_path",
path: std.bstring(`brioche-run.d/recipe-${n}`),
},
],
},
recipeValue,
n + 1,
];
}
}
1 change: 1 addition & 0 deletions packages/std/extra/index.bri
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { autopack, type AutopackOptions } from "./autopack.bri";
export * from "./oci_container_image.bri";
export * from "./run_bash.bri";
export * from "./runnable.bri";
export * from "./bash_runnable.bri";
export * from "./set_env.bri";
export * from "./with_runnable_link.bri";
Loading

0 comments on commit 35e3bf3

Please sign in to comment.