From a231edbeea0783d4b70906aa4377b6e076762986 Mon Sep 17 00:00:00 2001 From: Fluctlight Kayaba Date: Fri, 9 Feb 2024 01:52:55 +0700 Subject: [PATCH] feat: add Nim runtime to Function constructs --- packages/sst/src/constructs/Function.ts | 54 ++++++++++++ packages/sst/src/runtime/handlers.ts | 2 + packages/sst/src/runtime/handlers/nim.ts | 105 +++++++++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 packages/sst/src/runtime/handlers/nim.ts diff --git a/packages/sst/src/constructs/Function.ts b/packages/sst/src/constructs/Function.ts index 94fb84ef10..45046905b7 100644 --- a/packages/sst/src/constructs/Function.ts +++ b/packages/sst/src/constructs/Function.ts @@ -85,6 +85,7 @@ const supportedRuntimes = { java21: CDKRuntime.JAVA_21, "go1.x": CDKRuntime.PROVIDED_AL2, go: CDKRuntime.PROVIDED_AL2, + nim: CDKRuntime.PROVIDED_AL2, }; export type Runtime = keyof typeof supportedRuntimes; @@ -170,6 +171,11 @@ export interface FunctionProps */ python?: PythonProps; + /** + * Used to configure nim function properties + */ + nim?: NimProps; + /** * Used to configure container function properties */ @@ -745,6 +751,54 @@ export interface JavaProps { experimentalUseProvidedRuntime?: "provided" | "provided.al2"; } +/** + * Used to configure Nim package build options + */ +export interface NimProps { + /** + * Nimble build command to generate the bundled .zip file. + * @example + * ```js + * new Function(stack, "Function", { + * nim: { + * buildTask: "bundle" + * } + * }) + * ``` + */ + buildTask?: string; + /** + * Nimble build command to generate the bundled .zip file. + * + * @default [] + * + * @example + * ```js + * new Function(stack, "Function", { + * nim: { + * buildArgs: ["--threads:on", "-d:release", "-d:ssl"], + * } + * }) + * ``` + */ + buildArgs?: string[]; + /** + * The output folder that the bundled .zip file will be created within. + * + * @default "out" + * + * @example + * ```js + * new Function(stack, "Function", { + * nim: { + * buildOutputDir: "output" + * } + * }) + * ``` + */ + buildOutputDir?: string; +} + export interface ContainerProps { /** * Specify or override the CMD on the Docker image. diff --git a/packages/sst/src/runtime/handlers.ts b/packages/sst/src/runtime/handlers.ts index ffa8a2df5a..ad61acf800 100644 --- a/packages/sst/src/runtime/handlers.ts +++ b/packages/sst/src/runtime/handlers.ts @@ -14,6 +14,7 @@ import { useGoHandler } from "./handlers/go.js"; import { useJavaHandler } from "./handlers/java.js"; import { usePythonHandler } from "./handlers/python.js"; import { useRustHandler } from "./handlers/rust.js"; +import { useNimHandler } from "./handlers/nim.js"; import { lazy } from "../util/lazy.js"; import { Semaphore } from "../util/semaphore.js"; @@ -81,6 +82,7 @@ export const useRuntimeHandlers = lazy(() => { useJavaHandler(), useDotnetHandler(), useRustHandler(), + useNimHandler(), ]; const project = useProject(); const bus = useBus(); diff --git a/packages/sst/src/runtime/handlers/nim.ts b/packages/sst/src/runtime/handlers/nim.ts new file mode 100644 index 0000000000..f6b7c34ef5 --- /dev/null +++ b/packages/sst/src/runtime/handlers/nim.ts @@ -0,0 +1,105 @@ +import path from "path"; +import fs from "fs/promises"; +import { RuntimeHandler } from "../handlers.js"; +import { useRuntimeWorkers } from "../workers.js"; +import { ChildProcessWithoutNullStreams, exec, spawn } from "child_process"; +import { promisify } from "util"; +import { useRuntimeServerConfig } from "../server.js"; +import { findBelow, isChild } from "../../util/fs.js"; +import { useProject } from "../../project.js"; +const execAsync = promisify(exec); + +export const useNimHandler = (): RuntimeHandler => { + const processes = new Map(); + const sources = new Map(); + const handlerName = process.platform === "win32" ? 'bootstrap.exe' : 'bootstrap'; + + return { + shouldBuild: (input) => { + const parent = sources.get(input.functionID); + if (!parent) return false; + return isChild(parent, input.file); + }, + canHandle: (input) => input.startsWith("nim"), + startWorker: async (input) => { + const workers = await useRuntimeWorkers(); + const server = await useRuntimeServerConfig(); + const proc = spawn(path.join(input.out, handlerName), { + env: { + ...process.env, + ...input.environment, + IS_LOCAL: "true", + RUST_BACKTRACE: "1", + AWS_LAMBDA_RUNTIME_API: `http://localhost:${server.port}/${input.workerID}`, + AWS_LAMBDA_FUNCTION_MEMORY_SIZE: "1024", + }, + cwd: input.out, + }); + proc.on("exit", () => workers.exited(input.workerID)); + proc.stdout.on("data", (data: Buffer) => { + workers.stdout(input.workerID, data.toString()); + }); + proc.stderr.on("data", (data: Buffer) => { + workers.stdout(input.workerID, data.toString()); + }); + processes.set(input.workerID, proc); + }, + stopWorker: async (workerID) => { + const proc = processes.get(workerID); + if (proc) { + proc.kill(); + processes.delete(workerID); + } + }, + build: async (input) => { + const project = useProject(); + const nimble = await findNimble(); + const parsed = path.parse(input.props.handler!); + const srcPath = nimble && await findBelow(project.paths.root, nimble); + const buildTask = input.props.nim?.buildTask; + const outputDir = input.props.nim?.buildOutputDir || "out"; + sources.set(input.functionID, nimble || project.paths.root); + + try { + if (buildTask) { + await execAsync(buildTask); + } else { + const buildArgs = input.props.nim?.buildArgs || []; + + if (input.mode === "deploy") { + buildArgs.push('-d:release'); + } + + if (input.props.architecture === 'arm_64') { + buildArgs.push('--cpu:arm64'); + buildArgs.push('--os:linux'); + } + + const args = buildArgs.map((i) => i.trim()).join(' '); + await execAsync(`nim c ${args} -o:${outputDir}/${parsed.name} ${input.props.handler}`, { cwd: srcPath }) + } + + await fs.cp( + path.join(outputDir, parsed.name), + path.join(input.out, "bootstrap"), + ); + } catch (error) { + return { + type: "error", + errors: [String(error)], + }; + } + + return { + type: "success", + handler: "bootstrap", + }; + }, + }; +}; + +async function findNimble(): Promise { + const files = await fs.readdir(process.cwd()) + return files.find(name => name.endsWith('.nimble')); +}; +