mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 14:06:08 +00:00
255 lines
9.1 KiB
Zig
255 lines
9.1 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
|
|
const args = @import("args");
|
|
|
|
const util = @import("../util.zig");
|
|
|
|
/// Command line options for the `bundle` command.
|
|
pub const Options = struct {
|
|
optimize: enum { Debug, ReleaseFast, ReleaseSmall } = .ReleaseFast,
|
|
arch: enum { x86, x86_64, aarch64, default } = .default,
|
|
os: enum { linux, macos, windows, default } = .default,
|
|
|
|
pub const meta = .{
|
|
.full_text =
|
|
\\Creates a deployment bundle.
|
|
\\
|
|
\\On Windows, `tar.exe` is used to generate a `.zip` file.
|
|
\\
|
|
\\On other operating systems, `tar` is used to generate a `.tar.gz` file.
|
|
\\
|
|
\\The deployment bundle contains a compiled executable with the `public/` and `static/`
|
|
\\directories included. This bundle can be copied to a deployment server, unpacked, and
|
|
\\launched in place.
|
|
,
|
|
.option_docs = .{
|
|
.optimize = "Set optimization level, must be one of { Debug, ReleaseFast, ReleaseSmall } (default: ReleaseFast)",
|
|
.arch = "Set build target CPU architecture, must be one of { x86_64, aarch64 } (default: Current CPU arch)",
|
|
.os = "Set build target operating system, must be one of { linux, macos, windows } (default: Current OS)",
|
|
},
|
|
};
|
|
};
|
|
|
|
/// Run the deployment bundle generator. Create an archive containing the Jetzig executable,
|
|
/// with `public/` and `static/` directories.
|
|
pub fn run(
|
|
allocator: std.mem.Allocator,
|
|
options: Options,
|
|
writer: anytype,
|
|
T: type,
|
|
main_options: T,
|
|
) !void {
|
|
if (builtin.os.tag == .windows) {
|
|
std.debug.print("Bundling on Windows is currently not supported.\n", .{});
|
|
std.process.exit(1);
|
|
}
|
|
|
|
if (main_options.options.help) {
|
|
try args.printHelp(Options, "jetzig bundle", writer);
|
|
return;
|
|
}
|
|
|
|
std.debug.print("Compiling bundle...\n", .{});
|
|
var cwd = try util.detectJetzigProjectDir();
|
|
defer cwd.close();
|
|
|
|
const path = try cwd.realpathAlloc(allocator, ".");
|
|
|
|
cwd.deleteTree(".bundle") catch {};
|
|
var tmpdir = try cwd.makeOpenPath(".bundle", .{});
|
|
defer cwd.deleteTree(".bundle") catch {};
|
|
defer tmpdir.close();
|
|
|
|
defer allocator.free(path);
|
|
|
|
const views_path = try std.fs.path.join(allocator, &[_][]const u8{ "src", "app", "views" });
|
|
defer allocator.free(views_path);
|
|
|
|
const maybe_executable = try zig_build_install(allocator, path, options);
|
|
if (maybe_executable == null) {
|
|
std.debug.print("Unable to locate compiled executable in {s}", .{path});
|
|
util.printFailure();
|
|
std.process.exit(1);
|
|
}
|
|
|
|
const executable = maybe_executable.?;
|
|
|
|
// * Create .bundle/
|
|
// * Compile exe in .bundle/
|
|
// * Rename exe to `{bundle-name}/server`
|
|
// * Copy `public`, `static` and any markdown files into `{bundle-name}/`
|
|
// * Create tarball inside `.bundle/` from `{bundle-name}/`
|
|
defer allocator.free(executable);
|
|
|
|
const exe_basename = std.fs.path.basename(executable);
|
|
|
|
// We don't use `std.fs.path.extension` here because the project name may have a `.` in it
|
|
// which would be truncated when no `.exe` extension is present (e.g. on Linux).
|
|
const exe_name_len = if (std.mem.endsWith(u8, executable, ".exe"))
|
|
exe_basename.len - 4
|
|
else
|
|
exe_basename.len;
|
|
const bundle_name = exe_basename[0..exe_name_len];
|
|
|
|
var bundle_dir = try tmpdir.makeOpenPath(bundle_name, .{});
|
|
defer bundle_dir.close();
|
|
|
|
const bundle_real_path = try bundle_dir.realpathAlloc(allocator, ".");
|
|
defer allocator.free(bundle_real_path);
|
|
|
|
const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ "bin", executable });
|
|
defer allocator.free(exe_path);
|
|
const renamed_exe_path = try std.fs.path.join(allocator, &[_][]const u8{ bundle_name, "server" });
|
|
defer allocator.free(renamed_exe_path);
|
|
|
|
try tmpdir.rename(exe_path, renamed_exe_path);
|
|
|
|
var tar_argv = std.ArrayList([]const u8).init(allocator);
|
|
defer tar_argv.deinit();
|
|
switch (builtin.os.tag) {
|
|
.windows => {}, // TODO
|
|
else => {
|
|
try tar_argv.appendSlice(&[_][]const u8{
|
|
"tar",
|
|
"-zcf",
|
|
"../bundle.tar.gz",
|
|
bundle_name,
|
|
});
|
|
},
|
|
}
|
|
var public_dir: ?std.fs.Dir = cwd.openDir("public", .{}) catch null;
|
|
defer if (public_dir) |*dir| dir.close();
|
|
|
|
var static_dir: ?std.fs.Dir = cwd.openDir("static", .{}) catch null;
|
|
defer if (static_dir) |*dir| dir.close();
|
|
|
|
if (public_dir != null) {
|
|
const dest_path = try std.fs.path.join(allocator, &[_][]const u8{ bundle_real_path, "public" });
|
|
defer allocator.free(dest_path);
|
|
try copyTree(allocator, cwd, "public", dest_path);
|
|
}
|
|
|
|
if (static_dir != null) {
|
|
const dest_path = try std.fs.path.join(allocator, &[_][]const u8{ bundle_real_path, "static" });
|
|
defer allocator.free(dest_path);
|
|
try copyTree(allocator, cwd, "static", dest_path);
|
|
}
|
|
|
|
var markdown_paths = std.ArrayList([]const u8).init(allocator);
|
|
try locateMarkdownFiles(allocator, cwd, views_path, &markdown_paths);
|
|
|
|
defer markdown_paths.deinit();
|
|
defer for (markdown_paths.items) |markdown_path| allocator.free(markdown_path);
|
|
for (markdown_paths.items) |markdown_path| {
|
|
if (std.fs.path.dirname(markdown_path)) |dirname| bundle_dir.makePath(dirname) catch {};
|
|
try cwd.copyFile(markdown_path, bundle_dir, markdown_path, .{});
|
|
}
|
|
|
|
const tmpdir_real_path = try tmpdir.realpathAlloc(allocator, ".");
|
|
defer allocator.free(tmpdir_real_path);
|
|
|
|
try util.runCommand(allocator, tmpdir_real_path, tar_argv.items);
|
|
|
|
switch (builtin.os.tag) {
|
|
.windows => {},
|
|
else => std.debug.print("Bundle `bundle.tar.gz` generated successfully.", .{}),
|
|
}
|
|
util.printSuccess();
|
|
}
|
|
|
|
fn locateMarkdownFiles(allocator: std.mem.Allocator, dir: std.fs.Dir, views_path: []const u8, paths: *std.ArrayList([]const u8)) !void {
|
|
var views_dir = try dir.openDir(views_path, .{ .iterate = true });
|
|
defer views_dir.close();
|
|
var walker = try views_dir.walk(allocator);
|
|
defer walker.deinit();
|
|
|
|
while (try walker.next()) |entry| {
|
|
if (std.mem.eql(u8, std.fs.path.extension(entry.path), ".md")) {
|
|
const path = try std.fs.path.join(allocator, &[_][]const u8{ views_path, entry.path });
|
|
try paths.append(path);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn zig_build_install(allocator: std.mem.Allocator, path: []const u8, options: Options) !?[]const u8 {
|
|
var install_argv = std.ArrayList([]const u8).init(allocator);
|
|
defer install_argv.deinit();
|
|
|
|
try install_argv.appendSlice(&[_][]const u8{ "zig", "build" });
|
|
|
|
switch (options.optimize) {
|
|
.ReleaseFast => try install_argv.append("-Doptimize=ReleaseFast"),
|
|
.ReleaseSmall => try install_argv.append("-Doptimize=ReleaseSmall"),
|
|
.Debug => try install_argv.append("-Doptimize=Debug"),
|
|
}
|
|
|
|
var target_buf = std.ArrayList([]const u8).init(allocator);
|
|
defer target_buf.deinit();
|
|
|
|
try target_buf.append("-Dtarget=");
|
|
switch (options.arch) {
|
|
.x86 => try target_buf.append("x86"),
|
|
.x86_64 => try target_buf.append("x86_64"),
|
|
.aarch64 => try target_buf.append("aarch64"),
|
|
.default => try target_buf.append(@tagName(builtin.cpu.arch)),
|
|
}
|
|
|
|
try target_buf.append("-");
|
|
|
|
switch (options.os) {
|
|
.linux => try target_buf.append("linux"),
|
|
.macos => try target_buf.append("macos"),
|
|
.windows => try target_buf.append("windows"),
|
|
.default => try target_buf.append(@tagName(builtin.os.tag)),
|
|
}
|
|
|
|
const target = try std.mem.concat(allocator, u8, target_buf.items);
|
|
defer allocator.free(target);
|
|
|
|
try install_argv.appendSlice(&[_][]const u8{
|
|
target, "--prefix", ".bundle", "install",
|
|
});
|
|
|
|
var project_dir = try std.fs.openDirAbsolute(path, .{});
|
|
defer project_dir.close();
|
|
project_dir.makePath(".bundle") catch {};
|
|
|
|
try util.runCommand(allocator, path, install_argv.items);
|
|
|
|
const install_bin_path = try std.fs.path.join(allocator, &[_][]const u8{ ".bundle", "bin" });
|
|
defer allocator.free(install_bin_path);
|
|
var install_dir = try project_dir.openDir(install_bin_path, .{ .iterate = true });
|
|
defer install_dir.close();
|
|
|
|
var install_walker = try install_dir.walk(allocator);
|
|
defer install_walker.deinit();
|
|
while (try install_walker.next()) |entry| {
|
|
// TODO: Figure out what to do when multiple exe files are found.
|
|
return try allocator.dupe(u8, entry.path);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
fn copyTree(
|
|
allocator: std.mem.Allocator,
|
|
src_dir: std.fs.Dir,
|
|
sub_path: []const u8,
|
|
dest_path: []const u8,
|
|
) !void {
|
|
var dir = try src_dir.openDir(sub_path, .{ .iterate = true });
|
|
defer dir.close();
|
|
|
|
var walker = try dir.walk(allocator);
|
|
defer walker.deinit();
|
|
|
|
std.fs.makeDirAbsolute(dest_path) catch {};
|
|
var dest_dir = try std.fs.openDirAbsolute(dest_path, .{});
|
|
defer dest_dir.close();
|
|
|
|
while (try walker.next()) |entry| {
|
|
if (std.fs.path.dirname(entry.path)) |dirname| dest_dir.makePath(dirname) catch {};
|
|
try dir.copyFile(entry.path, dest_dir, entry.path, .{});
|
|
}
|
|
}
|