Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/PLUGINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,11 @@ libraries (vendored C, packed assets). See `fizzy.plugin.addCModule` and
[pixi's `build.zig`](https://github.com/fizzyedit/pixi/blob/main/build.zig) for a worked example
with vendored C deps.

A plugin that ships a program of its own (a helper it starts, as the agent plugin starts
`fizzy-mcp`) builds it as usual and passes it to `fizzy.plugin.installBeside(b, exe, .{ .plugin =
"<id>" })`: `zig build install` then copies it into the plugin's own directory, beside the
library and still executable, where the plugin finds it at run time.

Under `create`, the dylib's C-ABI entry point (the five symbols the host looks up at `dlopen`
time — ABI fingerprint check, the `register` entry, and injection hooks for the
allocator/`*Host`/dvui-context/render-bridge — see §7 for the full list) is a tiny **generated**
Expand Down
32 changes: 27 additions & 5 deletions sdk/plugin_sdk.zig
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,28 @@ pub fn install(b: *std.Build, lib: *std.Build.Step.Compile, opts: InstallOptions
// convenience). Not for a wasm build: the browser has no plugins directory, and the desktop
// editor would only find a file it cannot open.
if (lib.rootModuleTarget().cpu.arch == .wasm32) return;
addDevInstall(b, lib, name, dest, b.fmt("install plugin '{s}' into fizzy plugins dir", .{name}));
}

pub const InstallBesideOptions = struct {
/// The plugin's manifest `id`: the directory the artifact joins.
plugin: []const u8,
};

/// Wire `zig build install` to drop a second artifact (a helper program the plugin starts, say)
/// into the plugin's own `{plugins}/{plugin}/` directory, beside the library `install` puts there,
/// under the artifact's own file name and with its permissions. Like `install`'s copy, a dev
/// convenience: a release ships what the plugin's `release.yml` uploads.
///
/// fizzy.plugin.installBeside(b, helper_exe, .{ .plugin = "agent" });
pub fn installBeside(b: *std.Build, artifact: *std.Build.Step.Compile, opts: InstallBesideOptions) void {
if (artifact.rootModuleTarget().cpu.arch == .wasm32) return;
addDevInstall(b, artifact, opts.plugin, artifact.out_filename, b.fmt("install '{s}' beside plugin '{s}'", .{ artifact.out_filename, opts.plugin }));
}

/// Copy `artifact` to `{plugins}/{dir_name}/{file_name}` on every `zig build install`, through
/// `plugin_sdk_install.zig`: Zig 0.17's build has no custom steps.
fn addDevInstall(b: *std.Build, artifact: *std.Build.Step.Compile, dir_name: []const u8, file_name: []const u8, step_name: []const u8) void {
const fizzy_dep = fizzyDep(b, .{ .target = b.graph.host, .optimize = .fast });
const dev_exe = b.addExecutable(.{
.name = "fizzy-plugin-dev-install",
Expand All @@ -264,13 +286,13 @@ pub fn install(b: *std.Build, lib: *std.Build.Step.Compile, opts: InstallOptions
}),
});
const dev = b.addRunArtifact(dev_exe);
dev.setName(b.fmt("install plugin '{s}' into fizzy plugins dir", .{name}));
dev.addArtifactArg(lib);
dev.addArgs(&.{ name, dest });
dev.setName(step_name);
dev.addArtifactArg(artifact);
dev.addArgs(&.{ dir_name, file_name });
// A relative `FIZZY_PROFILE` is taken from the plugin's own directory.
dev.setCwd(b.path("."));
// Every install, not only when the dylib changed: the copy in the plugins dir may have been
// replaced or removed since.
// Every install, not only when the artifact changed: the copy in the plugins dir may have
// been replaced or removed since.
dev.has_side_effects = true;
b.getInstallStep().dependOn(&dev.step);
}
Expand Down
14 changes: 8 additions & 6 deletions sdk/plugin_sdk_install.zig
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Copy a built plugin into fizzy's plugins directory: `zig build install`'s dev convenience,
//! wired by `fizzy.plugin.install`. A program run by a Run step, because Zig 0.17's build has no
//! custom steps.
//! Copy a built plugin, or a program it installs beside itself, into fizzy's plugins directory:
//! `zig build install`'s dev convenience, wired by `fizzy.plugin.install` and `.installBeside`.
//! A program run by a Run step, because Zig 0.17's build has no custom steps.
//!
//! Usage: `plugin_sdk_install <built dylib> <name> <file name>`. It lands at
//! Usage: `plugin_sdk_install <built artifact> <name> <file name>`. It lands at
//! `<plugins>/<name>/<file name>`, where `<plugins>` is `$FIZZY_PROFILE/plugins` when that is set
//! (a relative profile is taken from the working directory, the plugin's own) and otherwise the
//! host's fizzy plugins directory, the one the editor scans. Read here, at make time, so the
Expand Down Expand Up @@ -34,14 +34,16 @@ pub fn main(init: std.process.Init) !void {

const dest = try std.fs.path.join(arena, &.{ dir, file_name });
const data = try cwd.readFileAlloc(io, src, arena, .limited(512 * 1024 * 1024));
// The source's permissions, so a program installed beside the plugin stays executable.
const permissions = (try cwd.statFile(io, src, .{})).permissions;
// Sibling temp file + rename, not a truncating write onto `dest`: a running fizzy may be
// reading `dest` right now (`PluginLoader.stableLoadCopyPath` copies from it), and on Windows
// a plain overwrite of a file the editor still has open fails outright. The rename also means
// a crash mid-write never leaves a half-written dylib at the load path.
const tmp = try std.fmt.allocPrint(arena, "{s}.part", .{dest});
try cwd.writeFile(io, .{ .sub_path = tmp, .data = data });
try cwd.writeFile(io, .{ .sub_path = tmp, .data = data, .flags = .{ .permissions = permissions } });
try std.Io.Dir.rename(cwd, tmp, cwd, dest, io);
std.log.info("fizzy: installed plugin → {s}", .{dest});
std.log.info("fizzy: installed {s} → {s}", .{ file_name, dest });
}

/// `{local_config}/fizzy/plugins`, mirroring the runtime loader (`core/paths.zig`), or
Expand Down
Loading