lambda-zig/lambdabuild.zig
Emil Lerch f444697d93
All checks were successful
Lambda-Zig Build / build (push) Successful in 1m27s
use exe name as the default if nothing else provided
2026-02-04 11:38:05 -08:00

190 lines
7.2 KiB
Zig

//! Lambda Build Integration for Zig Build System
//!
//! This module provides build steps for packaging and deploying Lambda functions.
//! It builds the lambda-build CLI tool and invokes it for each operation.
const std = @import("std");
/// Configuration options for Lambda build integration.
///
/// These provide project-level defaults that can still be overridden
/// via command-line options (e.g., `-Dfunction-name=...`).
pub const Config = struct {
/// Default function name if not specified via -Dfunction-name.
/// If null, falls back to the executable name (exe.name).
default_function_name: ?[]const u8 = null,
/// Default IAM role name if not specified via -Drole-name.
default_role_name: []const u8 = "lambda_basic_execution",
/// Default environment file if not specified via -Denv-file.
/// If the file doesn't exist, it's silently skipped.
default_env_file: ?[]const u8 = ".env",
/// Default AWS service principal to grant invoke permission.
/// For Alexa skills, use "alexa-appkit.amazon.com".
default_allow_principal: ?[]const u8 = null,
};
/// Information about the configured Lambda build steps.
///
/// Returned by `configureBuild` to allow consumers to depend on steps
/// and access deployment outputs.
pub const BuildInfo = struct {
/// Package step - creates the deployment zip
package_step: *std.Build.Step,
/// IAM step - creates/verifies the IAM role
iam_step: *std.Build.Step,
/// Deploy step - deploys the function to AWS Lambda
deploy_step: *std.Build.Step,
/// Invoke step - invokes the deployed function
invoke_step: *std.Build.Step,
/// LazyPath to JSON file with deployment info.
/// Contains: arn, function_name, region, account_id, role_arn, architecture, environment_keys
/// Available after deploy_step completes.
deploy_output: std.Build.LazyPath,
/// The function name used for deployment
function_name: []const u8,
};
/// Configure Lambda build steps for a Zig project.
///
/// Adds the following build steps:
/// - awslambda_package: Package the function into a zip file
/// - awslambda_iam: Create/verify IAM role
/// - awslambda_deploy: Deploy the function to AWS
/// - awslambda_run: Invoke the deployed function
///
/// The `config` parameter allows setting project-level defaults that can
/// still be overridden via command-line options.
///
/// Returns a `BuildInfo` struct containing references to all steps and
/// a `deploy_output` LazyPath to the deployment info JSON file.
pub fn configureBuild(
b: *std.Build,
lambda_build_dep: *std.Build.Dependency,
exe: *std.Build.Step.Compile,
config: Config,
) !BuildInfo {
// Get the lambda-build CLI artifact from the dependency
const cli = lambda_build_dep.artifact("lambda-build");
// Get configuration options (command-line overrides config defaults)
const function_name = b.option([]const u8, "function-name", "Function name for Lambda") orelse config.default_function_name orelse exe.name;
const region = b.option([]const u8, "region", "AWS region") orelse null;
const profile = b.option([]const u8, "profile", "AWS profile") orelse null;
const role_name = b.option(
[]const u8,
"role-name",
"IAM role name (default: lambda_basic_execution)",
) orelse config.default_role_name;
const payload = b.option(
[]const u8,
"payload",
"Lambda invocation payload",
) orelse "{}";
const env_file = b.option(
[]const u8,
"env-file",
"Path to environment variables file (KEY=VALUE format)",
) orelse config.default_env_file;
const allow_principal = b.option(
[]const u8,
"allow-principal",
"AWS service principal to grant invoke permission (e.g., alexa-appkit.amazon.com)",
) orelse config.default_allow_principal;
// Determine architecture for Lambda
const target_arch = exe.root_module.resolved_target.?.result.cpu.arch;
const arch_str = blk: {
switch (target_arch) {
.aarch64 => break :blk "aarch64",
.x86_64 => break :blk "x86_64",
else => {
std.log.warn("Unsupported architecture for Lambda: {}, defaulting to x86_64", .{target_arch});
break :blk "x86_64";
},
}
};
// Package step - output goes to cache based on input hash
const package_cmd = b.addRunArtifact(cli);
package_cmd.step.name = try std.fmt.allocPrint(b.allocator, "{s} package", .{cli.name});
package_cmd.addArgs(&.{ "package", "--exe" });
package_cmd.addFileArg(exe.getEmittedBin());
package_cmd.addArgs(&.{"--output"});
const zip_output = package_cmd.addOutputFileArg("function.zip");
package_cmd.step.dependOn(&exe.step);
const package_step = b.step("awslambda_package", "Package the Lambda function");
package_step.dependOn(&package_cmd.step);
// IAM step
const iam_cmd = b.addRunArtifact(cli);
iam_cmd.step.name = try std.fmt.allocPrint(b.allocator, "{s} iam", .{cli.name});
if (profile) |p| iam_cmd.addArgs(&.{ "--profile", p });
if (region) |r| iam_cmd.addArgs(&.{ "--region", r });
iam_cmd.addArgs(&.{ "iam", "--role-name", role_name });
const iam_step = b.step("awslambda_iam", "Create/verify IAM role for Lambda");
iam_step.dependOn(&iam_cmd.step);
// Deploy step (depends on package)
const deploy_cmd = b.addRunArtifact(cli);
deploy_cmd.step.name = try std.fmt.allocPrint(b.allocator, "{s} deploy", .{cli.name});
if (profile) |p| deploy_cmd.addArgs(&.{ "--profile", p });
if (region) |r| deploy_cmd.addArgs(&.{ "--region", r });
deploy_cmd.addArgs(&.{
"deploy",
"--function-name",
function_name,
"--zip-file",
});
deploy_cmd.addFileArg(zip_output);
deploy_cmd.addArgs(&.{
"--role-name",
role_name,
"--arch",
arch_str,
});
if (env_file) |ef| deploy_cmd.addArgs(&.{ "--env-file", ef });
if (allow_principal) |ap| deploy_cmd.addArgs(&.{ "--allow-principal", ap });
// Add deploy output file for deployment info JSON
deploy_cmd.addArg("--deploy-output");
const deploy_output = deploy_cmd.addOutputFileArg("deploy-output.json");
deploy_cmd.step.dependOn(&package_cmd.step);
const deploy_step = b.step("awslambda_deploy", "Deploy the Lambda function");
deploy_step.dependOn(&deploy_cmd.step);
// Invoke/run step (depends on deploy)
const invoke_cmd = b.addRunArtifact(cli);
invoke_cmd.step.name = try std.fmt.allocPrint(b.allocator, "{s} invoke", .{cli.name});
if (profile) |p| invoke_cmd.addArgs(&.{ "--profile", p });
if (region) |r| invoke_cmd.addArgs(&.{ "--region", r });
invoke_cmd.addArgs(&.{
"invoke",
"--function-name",
function_name,
"--payload",
payload,
});
invoke_cmd.step.dependOn(&deploy_cmd.step);
const run_step = b.step("awslambda_run", "Invoke the deployed Lambda function");
run_step.dependOn(&invoke_cmd.step);
return .{
.package_step = package_step,
.iam_step = iam_step,
.deploy_step = deploy_step,
.invoke_step = run_step,
.deploy_output = deploy_output,
.function_name = function_name,
};
}