From 59f5dc16c843bef1b2d7277a03a0d92c4aad992a Mon Sep 17 00:00:00 2001 From: Kyle Lacy Date: Thu, 30 May 2024 15:42:13 -0700 Subject: [PATCH] Add `std.runnableBashScript` --- projects/std/core/index.bri | 2 +- projects/std/extra/index.bri | 1 + projects/std/extra/runnable_bash_script.bri | 172 ++++++++++++++++++++ projects/std/runnable_tools.bri | 61 +++++++ 4 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 projects/std/extra/runnable_bash_script.bri create mode 100644 projects/std/runnable_tools.bri diff --git a/projects/std/core/index.bri b/projects/std/core/index.bri index 9f04f7a..0005c51 100644 --- a/projects/std/core/index.bri +++ b/projects/std/core/index.bri @@ -1,6 +1,6 @@ export * from "./recipes"; export { source } from "./source.bri"; -export { assert, unreachable, indoc, type Awaitable } from "./utils.bri"; +export { assert, unreachable, indoc, mixin, type Awaitable } from "./utils.bri"; export { utf8Encode, utf8Decode, diff --git a/projects/std/extra/index.bri b/projects/std/extra/index.bri index b9351fa..36e4f82 100644 --- a/projects/std/extra/index.bri +++ b/projects/std/extra/index.bri @@ -1,4 +1,5 @@ export * from "./autowrap.bri"; export * from "./run_bash.bri"; +export * from "./runnable_bash_script.bri"; export * from "./set_env.bri"; export * from "./set_run.bri"; diff --git a/projects/std/extra/runnable_bash_script.bri b/projects/std/extra/runnable_bash_script.bri new file mode 100644 index 0000000..8191922 --- /dev/null +++ b/projects/std/extra/runnable_bash_script.bri @@ -0,0 +1,172 @@ +import * as std from "/core"; +import { tools } from "/toolchain"; +import { + type RunnableTemplate, + makeRunnableExecutable, +} from "/runnable_tools.bri"; + +export type RunnableBashScript = std.Recipe & + RunnableBashScriptUtils; + +export interface RunnableBashScriptUtils { + env(values: Record): RunnableBashScript; + dependencies( + ...dependencies: std.AsyncRecipe[] + ): RunnableBashScript; +} + +export function runnableBashScript( + strings: TemplateStringsArray, + ...values: string[] +): RunnableBashScript { + const script = std.indoc(strings, ...values); + return makeRunnableBashScript({ + script, + env: { + artifact: { relativePath: "." }, + }, + dependencies: [], + }); +} + +interface RunnableBashScriptOptions { + script: string; + env: Record; + dependencies: std.AsyncRecipe[]; +} + +function makeRunnableBashScript( + options: RunnableBashScriptOptions, +): RunnableBashScript { + let recipe = std.directory(); + 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 = {}; + 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, + }); + + recipe = recipe.insert("brioche-run", runnable); + + return std.mixin(recipe, { + env(values: Record): RunnableBashScript { + return makeRunnableBashScript({ + script: options.script, + env: { ...options.env, ...values }, + dependencies: options.dependencies, + }); + }, + + dependencies( + ...dependencies: std.AsyncRecipe[] + ): RunnableBashScript { + return makeRunnableBashScript({ + script: options.script, + env: options.env, + dependencies: [...options.dependencies, ...dependencies], + }); + }, + }); +} + +type RunnableTemplateValue = + | string + | undefined + | { relativePath: string } + | std.AsyncRecipe + | RunnableTemplateValue[]; + +function buildTemplate( + template: RunnableTemplateValue, + recipe: std.Recipe, + n: number, +): [RunnableTemplate, std.Recipe, number] { + if (template == null || template === "") { + return [{ components: [] }, recipe, n]; + } else if (typeof template === "string") { + return [ + { components: [{ type: "literal", value: std.bstring(template) }] }, + recipe, + n, + ]; + } else if (Array.isArray(template)) { + const resultComponents = []; + for (const component of template) { + let result: RunnableTemplate; + [result, recipe, n] = buildTemplate(component, recipe, n); + + resultComponents.push(...result.components); + } + + return [{ components: resultComponents }, recipe, n]; + } else if ("relativePath" in template) { + return [ + { + components: [ + { type: "relative_path", path: std.bstring(template.relativePath) }, + ], + }, + recipe, + n, + ]; + } else { + recipe = recipe.insert(`brioche-run.d/recipe-${n}`, template); + return [ + { + components: [ + { + type: "relative_path", + path: std.bstring(`brioche-run.d/recipe-${n}`), + }, + ], + }, + recipe, + n + 1, + ]; + } +} diff --git a/projects/std/runnable_tools.bri b/projects/std/runnable_tools.bri new file mode 100644 index 0000000..0793af7 --- /dev/null +++ b/projects/std/runnable_tools.bri @@ -0,0 +1,61 @@ +import * as std from "/core"; + +export function runtimeUtils(): std.Recipe { + return std + .download({ + url: "https://development-content.brioche.dev/github.com/brioche-dev/brioche-runtime-utils/commits/fc48540924428944e30a4901c2fff2135abf5342/x86_64-linux/brioche-runtime-utils.tar.zstd", + hash: std.sha256Hash( + "c1ea132ed08abd7f719a698cdc32e2444e6f5ca0792c7737d42a41771a04cfa7", + ), + }) + .unarchive("tar", "zstd"); +} + +interface RunnableData { + command: RunnableTemplate; + args: RunnableTemplate[]; + env: Record; +} + +export function makeRunnableExecutable( + data: RunnableData, +): std.Recipe { + return std + .process({ + command: runtimeUtils().get("bin/runnable"), + args: [ + "make-runnable", + "--runnable", + runtimeUtils().get("bin/start-runnable"), + "--output", + std.outputPath, + "--runnable-data", + JSON.stringify(data), + ], + }) + .cast("file"); +} + +// function template(template: RunnableTemplate): RunnableTemplateJson { +// return { +// components: [template].flat().map((component) => { +// if (Array.isArray(component)) { +// throw new Error("Expected template to be flattened"); +// } + +// if (typeof component === "string") { +// return { type: "literal", value: std.tickEncode(component) }; +// } else { +// return { type: "relative_path", path: std.tickEncode(component.path) }; +// } +// }), +// }; +// } + +export interface RunnableTemplate { + components: RunnableTemplateComponent[]; +} + +export type RunnableTemplateComponent = + | { type: "literal"; value: std.BString } + | { type: "relative_path"; path: std.BString };