Skip to content

Commit

Permalink
refactor: Rewrite wf-lua in zig :)
Browse files Browse the repository at this point in the history
Want to reduce my c++ usage in order to preserve my mental health :P

This should build fine anywhere zig is present. clang++ is still a
dependency since the zig c++ compiler differs in ABI to clang/gcc.
See: ziglang/zig#9832
  • Loading branch information
Javyre committed Nov 22, 2021
1 parent 816f92e commit 86d028f
Show file tree
Hide file tree
Showing 20 changed files with 772 additions and 331 deletions.
6 changes: 6 additions & 0 deletions .gitignore
@@ -0,0 +1,6 @@
# Zig
/zig-cache/
/zig-out/

# LDoc
/public/
208 changes: 208 additions & 0 deletions build.zig
@@ -0,0 +1,208 @@
const std = @import("std");

const Builder = std.build.Builder;
const Step = std.build.Step;
const WriteFileStep = std.build.WriteFileStep;
const InstallDir = std.build.InstallDir;

pub fn build(b: *Builder) !void {
// Standard release options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
const mode = b.standardReleaseOptions();

// TODO: read this path from pkgconfig variable of wayfire.
const wf_metadata_dir = "share/wayfire/metadata";
// TODO: make this path configurable.
const lua_runtime_dir = "share/wayfire/lua";

const cpp_opts = [_][]const u8{
"-Wall",
"-Wextra",
"-Werror",
"-O3",
"-g",
"-std=c++17",
"-fPIC",
"-DWLR_USE_UNSTABLE",
"-DWAYFIRE_PLUGIN",
};
const cpp_command =
"clang++ $(pkg-config --cflags wayfire) " ++
"-Wall -Wextra -Werror -O3 -g -std=c++17 " ++
"-fPIC -DWLR_USE_UNSTABLE -DWAYFIRE_PLUGIN ";

// TODO: cache this somehow. (makefile?)
const cpp_sources = [_][]const u8{ "wf", "wf-plugin" };
var cpp_objs = std.ArrayList(*std.build.RunStep).init(b.allocator);
var cpp_cleanup = std.ArrayList(*std.build.RunStep).init(b.allocator);
inline for (cpp_sources) |source| {
try cpp_objs.append(b.addSystemCommand(&[_][]const u8{
"sh",
"-c",
b.fmt(
cpp_command ++
"-c src/" ++ source ++ ".cpp " ++
"-o {s}/" ++ source ++ ".o",
.{b.cache_root},
),
}));
try cpp_cleanup.append(b.addSystemCommand(&[_][]const u8{
"rm", b.fmt("{s}/" ++ source ++ ".o", .{b.cache_root}),
}));
}

const plugin = b.addSharedLibrary(
"wf-lua",
"src/wf-lua.zig",
.unversioned,
);
{
inline for (cpp_sources) |source| {
plugin.addObjectFile(
b.fmt("{s}/" ++ source ++ ".o", .{b.cache_root}),
);
}
plugin.addIncludeDir("src");
// TODO: change to linkLibCpp when zig version is bumped
plugin.linkSystemLibrary("c++");
plugin.linkSystemLibrary("wayfire");
plugin.linkSystemLibrary("wf-config");
plugin.linkSystemLibrary("wlroots");
plugin.linkSystemLibrary("luajit");

plugin.defineCMacro("WLR_USE_UNSTABLE");
plugin.defineCMacro("WAYFIRE_PLUGIN");
plugin.addBuildOption(
[]const u8,
"LUA_RUNTIME",
b.fmt("{s}/{s}", .{ b.install_prefix, lua_runtime_dir }),
);

plugin.setBuildMode(mode);
plugin.override_dest_dir = InstallDir{ .Custom = "lib/wayfire/" };
plugin.force_pic = true;
plugin.rdynamic = true;
// plugin.install();
const install_plugin = b.addInstallArtifact(plugin);

for (cpp_objs.items) |obj_step| {
plugin.step.dependOn(&obj_step.step);
}
for (cpp_cleanup.items) |cleanup| {
cleanup.step.dependOn(&plugin.step);
install_plugin.step.dependOn(&cleanup.step);
}
b.getInstallStep().dependOn(&install_plugin.step);
}

b.installFile(
"metadata/wf-lua.xml",
b.fmt("{s}/wf-lua.xml", .{wf_metadata_dir}),
);

const gen_lua_header = b.addWriteFile("wf_h.lua.out", gen_lua_header: {
var src = std.ArrayList(u8).init(b.allocator);
const writer = src.writer();
try writer.writeAll(
\\ local ffi = require 'ffi'
\\
\\ ffi.cdef [[
\\
);

try writeAllFile(writer, b.fmt("{s}/src/wf.h", .{b.build_root}));
try writeAllFile(writer, b.fmt("{s}/src/wf-lua.h", .{b.build_root}));

try writer.writeAll(
\\ ]]
);
break :gen_lua_header src.items;
});

b.installFile("lua/wf.lua", b.fmt("{s}/wf.lua", .{lua_runtime_dir}));
b.installDirectory(.{
.source_dir = "lua/wf",
.install_dir = InstallDir{ .Custom = lua_runtime_dir },
.install_subdir = "wf",
});
installFromWriteFile(.{
.builder = b,
.wfs = gen_lua_header,
.base_name = "wf_h.lua.out",
.dest_rel_path = b.fmt("{s}/wf/wf_h.lua", .{lua_runtime_dir}),
});

var main_tests = b.addTest("src/wf-lua.zig");
main_tests.setBuildMode(mode);

const test_step = b.step("test", "Run library tests");
test_step.dependOn(&main_tests.step);
}

const InstallFromWriteFileOpts = struct {
builder: *Builder,
wfs: *WriteFileStep,
base_name: []const u8,
dest_rel_path: []const u8,
};

fn installFromWriteFile(opts: InstallFromWriteFileOpts) void {
const step = InstallFromWriteFileStep.create(opts);
step.step.dependOn(&opts.wfs.step);
opts.builder.getInstallStep().dependOn(&step.step);
}

const InstallFromWriteFileStep = struct {
const This = @This();

builder: *Builder,
wfs: *WriteFileStep,
base_name: []const u8,
dest_rel_path: []const u8,
step: Step,

pub fn create(opts: InstallFromWriteFileOpts) *This {
const self = opts.builder.allocator.create(This) catch unreachable;

self.* = .{
.builder = opts.builder,
.wfs = opts.wfs,
.base_name = opts.base_name,
.dest_rel_path = opts.dest_rel_path,
.step = Step.init(
.Custom,
opts.builder.fmt("install generated {s}", .{opts.base_name}),
opts.builder.allocator,
This.make,
),
};
return self;
}

fn make(step: *Step) !void {
const self = @fieldParentPtr(This, "step", step);
const src_path = self.wfs.getOutputPath(self.base_name);
const full_src_path = self.builder.pathFromRoot(src_path);

const full_dest_path = self.builder.getInstallPath(
.Prefix,
self.dest_rel_path,
);
try self.builder.updateFile(full_src_path, full_dest_path);
}
};

/// Write a file's contents to a writer.
/// Path must be absolute.
fn writeAllFile(writer: anytype, path: []const u8) !void {
const file = try std.fs.openFileAbsolute(path, .{});
defer file.close();

var buffer: [256]u8 = undefined;
while (true) {
const amt = try file.read(&buffer);
if (amt == 0)
break;
try writer.writeAll(buffer[0..amt]);
}
}
11 changes: 0 additions & 11 deletions lua/meson.build

This file was deleted.

23 changes: 11 additions & 12 deletions lua/wf.lua
Expand Up @@ -5,13 +5,12 @@
-- @alias Wf
-- @module wf
--
require 'wf.wf_h' -- Load the wf.h c header.

local ffi = require 'ffi'
local util = require 'wf.util'
local Log = require 'wf.log'

-- Load the wf.h c header.
require 'wf.wf_h'

-- FFI Logic

local Raw = {lifetime_callbacks = {}, signal_callbacks = {}}
Expand All @@ -22,10 +21,10 @@ end

-- There is a cap on the amount of allowed lua-created C callbacks so we
-- reuse this same one for all signals.
Raw.event_callback = ffi.cast("wf_EventCallback",
Raw.event_callback = ffi.cast("wflua_EventCallback",
function(emitter, event_type, signal, signal_data)
local success, err = pcall(function()
if event_type == ffi.C.WF_EVENT_TYPE_SIGNAL then
if event_type == ffi.C.WFLUA_EVENT_TYPE_SIGNAL then
-- NOTE: The address of the emitter is assumed to be constant for any
-- given emitter.
-- A signal was emitted to the signal_connection.
Expand All @@ -36,7 +35,7 @@ Raw.event_callback = ffi.cast("wf_EventCallback",
.signals
emitter_signals[ffi.string(signal)]:call(emitter, signal_data)

elseif event_type == ffi.C.WF_EVENT_TYPE_EMITTER_DESTROYED then
elseif event_type == ffi.C.WFLUA_EVENT_TYPE_EMITTER_DESTROYED then
Log.debug('EMITTER DIED: ', object_id(emitter))

local emitter_id = object_id(emitter)
Expand All @@ -54,7 +53,7 @@ function Raw:subscribe_lifetime(emitter_ptr, handler)
Log.debug('subscribing to ' .. emitter .. ' lifetime')

if not self.lifetime_callbacks[emitter] then
ffi.C.wf_lifetime_subscribe(emitter_ptr)
ffi.C.wflua_lifetime_subscribe(emitter_ptr)
self.lifetime_callbacks[emitter] = util.Hook {handler}
else
self.lifetime_callbacks[emitter]:hook(handler)
Expand All @@ -68,7 +67,7 @@ function Raw:unsubscribe_lifetime(emitter_ptr, handler)
self.lifetime_callbacks[emitter]:unhook(handler)

if self.lifetime_callbacks[emitter]:is_empty() then
ffi.C.wf_lifetime_unsubscribe(emitter_ptr)
ffi.C.wflua_lifetime_unsubscribe(emitter_ptr)
self.lifetime_callbacks[emitter] = nil
end
end
Expand All @@ -83,15 +82,15 @@ function Raw:subscribe(emitter_ptr, signal, handler)
-- Clean up when the C++ emitter object dies.
lifetime_handler = self:subscribe_lifetime(emitter_ptr, function()
self.signal_callbacks[emitter] = nil
ffi.C.wf_signal_unsubscribe_all(emitter_ptr)
ffi.C.wflua_signal_unsubscribe_all(emitter_ptr)
end),
signals = {}
}
end

local emitter_cbs = self.signal_callbacks[emitter].signals
if not emitter_cbs[signal] then
ffi.C.wf_signal_subscribe(emitter_ptr, signal)
ffi.C.wflua_signal_subscribe(emitter_ptr, signal)

emitter_cbs[signal] = util.Hook {handler}
else
Expand All @@ -108,7 +107,7 @@ function Raw:unsubscribe(emitter_ptr, signal, handler)
emitter_cbs[signal]:unhook(handler)

if emitter_cbs[signal]:is_empty() then
ffi.C.wf_signal_unsubscribe(emitter_ptr, signal)
ffi.C.wflua_signal_unsubscribe(emitter_ptr, signal)

emitter_cbs[signal] = nil

Expand All @@ -122,7 +121,7 @@ function Raw:unsubscribe(emitter_ptr, signal, handler)
end
end

ffi.C.wf_register_event_callback(Raw.event_callback)
ffi.C.wflua_register_event_callback(Raw.event_callback)

--- Set the value of an option.
-- The option must already by registered by Wayfire.
Expand Down
22 changes: 8 additions & 14 deletions lua/wf/log.lua
@@ -1,24 +1,18 @@
local Log = {
log_level = 0
}
local ffi = require 'ffi'

local Log = {}

local function to_strings(list)
for i, a in ipairs(list) do list[i] = tostring(a) end
return list
end

local function log(lvl, color, ...)
if Log.log_level < lvl then
return
end

io.stderr:write('[' .. color .. 'm')
io.stderr:write(unpack(to_strings({...})))
io.stderr:write('\n')
local function log(lvl, ...)
ffi.C.wflua_log(lvl, table.concat(to_strings({...}), ' '))
end

function Log.err(...) return log(0, 31, 'EE: ', ...) end
function Log.warn(...) return log(1, 33, 'WW: ', ...) end
function Log.debug(...) return log(2, 0, 'DD: ', ...) end
function Log.err(...) return log(ffi.C.WFLUA_LOGLVL_ERR, ...) end
function Log.warn(...) return log(ffi.C.WFLUA_LOGLVL_WARN, ...) end
function Log.debug(...) return log(ffi.C.WFLUA_LOGLVL_DEBUG, ...) end

return Log
5 changes: 0 additions & 5 deletions lua/wf/wf_h.lua.in

This file was deleted.

0 comments on commit 86d028f

Please sign in to comment.