Integrate scanner with the zig build system

This commit is contained in:
Isaac Freund
2020-11-01 12:59:35 +01:00
parent 62c3dbdc9a
commit b6cbd99882
7 changed files with 285 additions and 173 deletions
-6
View File
@@ -1,7 +1 @@
zig-cache
client.zig
server.zig
common.zig
*_client.zig
*_server.zig
*_common.zig
+133 -42
View File
@@ -1,53 +1,144 @@
const mem = @import("std").mem;
const Builder = @import("std").build.Builder;
const std = @import("std");
const zbs = std.build;
pub fn build(b: *Builder) void {
pub fn build(b: *zbs.Builder) void {
const target = b.standardTargetOptions(.{});
const mode = b.standardReleaseOptions();
const examples = b.option(
bool,
"examples",
"Set to true to build examples",
) orelse false;
var scanner = ScanProtocolsStep.create(b, ".", .both);
for ([_][]const u8{ "globals", "listener", "seats" }) |example| {
const path = std.mem.concat(b.allocator, u8, &[_][]const u8{ "example/", example, ".zig" }) catch unreachable;
const exe = b.addExecutable(example, path);
exe.setTarget(target);
exe.setBuildMode(mode);
{
const scanner = b.addExecutable("scanner", "scanner.zig");
scanner.setTarget(target);
scanner.setBuildMode(mode);
exe.step.dependOn(&scanner.step);
exe.addPackage(scanner.getPkg());
scanner.link(exe);
scanner.install();
exe.install();
}
{
const test_files = [_][]const u8{ "scanner.zig", "src/common_core.zig" };
const test_step = b.step("test", "Run the tests");
for ([_][]const u8{ "src/scanner.zig", "src/common_core.zig" }) |file| {
const t = b.addTest(file);
t.setTarget(target);
t.setBuildMode(mode);
const test_step = b.step("test", "Run the tests");
for (test_files) |file| {
const t = b.addTest(file);
t.setTarget(target);
t.setBuildMode(mode);
test_step.dependOn(&t.step);
}
}
if (examples) {
const example_names = [_][]const u8{ "globals", "listener", "seats" };
for (example_names) |example| {
const path = mem.concat(b.allocator, u8, &[_][]const u8{ "example/", example, ".zig" }) catch unreachable;
const exe = b.addExecutable(example, path);
exe.setTarget(target);
exe.setBuildMode(mode);
exe.linkLibC();
exe.linkSystemLibrary("wayland-client");
// Requires the scanner to have been run for this to build
// TODO: integrate scanner with build system
exe.addPackagePath("wayland", "wayland.zig");
exe.install();
}
test_step.dependOn(&t.step);
}
}
pub const ScanProtocolsStep = struct {
const scanner = @import("src/scanner.zig");
builder: *zbs.Builder,
step: zbs.Step,
/// Relative path to the root of the zig wayland repo from the user's build.zig
zig_wayland_path: []const u8,
/// Whether to generate bindings for wayland-client, wayland-server, or both
target: scanner.Target,
/// Slice of absolute paths of protocol xml files to be scanned
protocol_paths: std.ArrayList([]const u8),
pub fn create(builder: *zbs.Builder, zig_wayland_path: []const u8, target: scanner.Target) *ScanProtocolsStep {
const self = builder.allocator.create(ScanProtocolsStep) catch unreachable;
self.* = .{
.builder = builder,
.step = zbs.Step.init(.Custom, "Scan Protocols", builder.allocator, make),
.zig_wayland_path = zig_wayland_path,
.target = target,
.protocol_paths = std.ArrayList([]const u8).init(builder.allocator),
};
return self;
}
/// Generate bindings from the protocol xml at the given path
pub fn addProtocolPath(self: *ScanProtocolsStep, path: []const u8) void {
self.protocol_paths.append(path) catch unreachable;
}
/// Generate bindings from protocol xml provided by the wayland-protocols
/// package given the relative path (e.g. "stable/xdg-shell/xdg-shell.xml")
pub fn addSystemProtocol(self: *ScanProtocolsStep, relative_path: []const u8) void {
const protocol_dir = std.fmt.trim(self.builder.exec(
&[_][]const u8{ "pkg-config", "--variable=pkgdatadir", "wayland-protocols" },
) catch unreachable);
self.addProtocolPath(std.fs.path.join(
self.builder.allocator,
&[_][]const u8{ protocol_dir, relative_path },
) catch unreachable);
}
fn make(step: *zbs.Step) !void {
const self = @fieldParentPtr(ScanProtocolsStep, "step", step);
const out_path = try std.fs.path.join(
self.builder.allocator,
&[_][]const u8{ self.zig_wayland_path, "generated" },
);
var out_dir = try std.fs.cwd().openDir(out_path, .{});
defer out_dir.close();
const wayland_dir = std.fmt.trim(try self.builder.exec(
&[_][]const u8{ "pkg-config", "--variable=pkgdatadir", "wayland-scanner" },
));
const wayland_xml = try std.fs.path.join(
self.builder.allocator,
&[_][]const u8{ wayland_dir, "wayland.xml" },
);
try scanner.scan(self.target, out_dir, wayland_xml, self.protocol_paths.items);
// Once https://github.com/ziglang/zig/issues/131 is implemented
// we can stop generating/linking C code.
for (self.protocol_paths.items) |path| {
_ = try self.builder.exec(
&[_][]const u8{ "wayland-scanner", "private-code", path, self.getCodePath(path) },
);
}
}
/// Link the given LibExeObjStep against libwayland and compile the
/// necessary C code.
pub fn link(self: *ScanProtocolsStep, obj: *zbs.LibExeObjStep) void {
obj.linkLibC();
switch (self.target) {
.client => obj.linkSystemLibrary("wayland-client"),
.server => obj.linkSystemLibrary("wayland-server"),
.both => {
obj.linkSystemLibrary("wayland-client");
obj.linkSystemLibrary("wayland-server");
},
}
for (self.protocol_paths.items) |path|
obj.addCSourceFile(self.getCodePath(path), &[_][]const u8{"-std=c99"});
}
pub fn getPkg(self: *ScanProtocolsStep) zbs.Pkg {
return .{
.name = "wayland",
.path = std.fs.path.join(
self.builder.allocator,
&[_][]const u8{ self.zig_wayland_path, "generated/wayland.zig" },
) catch unreachable,
};
}
fn getCodePath(self: *ScanProtocolsStep, xml_in_path: []const u8) []const u8 {
const allocator = self.builder.allocator;
// Extension is .xml, so slice off the last 4 characters
const basename = std.fs.path.basename(xml_in_path);
const basename_no_ext = basename[0..(basename.len - 4)];
const code_filename = std.fmt.allocPrint(allocator, "{}-protocol.c", .{basename_no_ext}) catch unreachable;
return std.fs.path.join(
allocator,
&[_][]const u8{ self.zig_wayland_path, "generated", code_filename },
) catch unreachable;
}
};
+2
View File
@@ -0,0 +1,2 @@
*
!.gitignore
+150 -125
View File
@@ -1,14 +1,137 @@
const std = @import("std");
const mem = std.mem;
const xml = @import("src/xml.zig");
const xml = @import("xml.zig");
const gpa = &allocator_instance.allocator;
var allocator_instance = std.heap.GeneralPurposeAllocator(.{}){};
const Target = enum {
pub const Target = enum {
client,
server,
both,
};
pub fn scan(target: Target, out_dir: std.fs.Dir, wayland_xml: []const u8, protocols: []const []const u8) !void {
const wayland_file = try out_dir.createFile("wayland.zig", .{});
try wayland_file.writeAll(@embedFile("wayland.zig"));
defer wayland_file.close();
var scanner = Scanner{};
try scanner.scanProtocol(target, out_dir, wayland_xml);
for (protocols) |xml_filename|
try scanner.scanProtocol(target, out_dir, xml_filename);
if (target == .client or target == .both) {
const client_core_file = try out_dir.createFile("wayland_client_core.zig", .{});
defer client_core_file.close();
try client_core_file.writeAll(@embedFile("wayland_client_core.zig"));
const client_file = try out_dir.createFile("client.zig", .{});
defer client_file.close();
const writer = client_file.writer();
var iter = scanner.client.iterator();
while (iter.next()) |entry| {
try writer.print("pub const {} = struct {{", .{entry.key});
if (mem.eql(u8, entry.key, "wl"))
try writer.writeAll("pub usingnamespace @import(\"wayland_client_core.zig\");\n");
for (entry.value.items) |generated_file|
try writer.print("pub usingnamespace @import(\"{}\");", .{generated_file});
try writer.writeAll("};\n");
}
}
if (target == .server or target == .both) {
const server_core_file = try out_dir.createFile("wayland_server_core.zig", .{});
defer server_core_file.close();
try server_core_file.writeAll(@embedFile("wayland_server_core.zig"));
const server_file = try out_dir.createFile("server.zig", .{});
defer server_file.close();
const writer = server_file.writer();
var iter = scanner.server.iterator();
while (iter.next()) |entry| {
try writer.print("pub const {} = struct {{", .{entry.key});
if (mem.eql(u8, entry.key, "wl"))
try writer.writeAll("pub usingnamespace @import(\"wayland_server_core.zig\");\n");
for (entry.value.items) |generated_file|
try writer.print("pub usingnamespace @import(\"{}\");", .{generated_file});
try writer.writeAll("};\n");
}
}
{
const common_file = try out_dir.createFile("common.zig", .{});
defer common_file.close();
const writer = common_file.writer();
try writer.writeAll(@embedFile("common_core.zig"));
var iter = scanner.common.iterator();
while (iter.next()) |entry| {
try writer.print("pub const {} = struct {{", .{entry.key});
for (entry.value.items) |generated_file|
try writer.print("pub usingnamespace @import(\"{}\");", .{generated_file});
try writer.writeAll("};\n");
}
}
}
const Side = enum {
client,
server,
};
const Scanner = struct {
/// Map from namespace to list of generated files
const Map = std.hash_map.StringHashMap(std.ArrayListUnmanaged([]const u8));
client: Map = Map.init(gpa),
server: Map = Map.init(gpa),
common: Map = Map.init(gpa),
fn scanProtocol(scanner: *Scanner, target: Target, out_dir: std.fs.Dir, xml_filename: []const u8) !void {
const xml_file = try std.fs.openFileAbsolute(xml_filename, .{});
defer xml_file.close();
var arena = std.heap.ArenaAllocator.init(gpa);
defer arena.deinit();
const xml_bytes = try xml_file.readToEndAlloc(&arena.allocator, 512 * 4096);
const protocol = try Protocol.parseXML(&arena.allocator, xml_bytes);
const xml_basename = std.fs.path.basename(xml_filename);
const protocol_name = try gpa.dupe(u8, xml_basename[0 .. xml_basename.len - 4]);
for (protocol_name) |*ch| {
if (ch.* == '-') ch.* = '_';
}
const protocol_namespace = try gpa.dupe(u8, protocol.namespace);
if (target == .client or target == .both) {
const client_filename = try mem.concat(gpa, u8, &[_][]const u8{ protocol_name, "_client.zig" });
const client_file = try out_dir.createFile(client_filename, .{});
defer client_file.close();
try protocol.emitClient(client_file.writer());
try (try scanner.client.getOrPutValue(protocol_namespace, .{})).value.append(gpa, client_filename);
}
if (target == .server or target == .both) {
const server_filename = try mem.concat(gpa, u8, &[_][]const u8{ protocol_name, "_server.zig" });
const server_file = try out_dir.createFile(server_filename, .{});
defer server_file.close();
try protocol.emitServer(server_file.writer());
try (try scanner.server.getOrPutValue(protocol_namespace, .{})).value.append(gpa, server_filename);
}
{
const common_filename = try mem.concat(gpa, u8, &[_][]const u8{ protocol_name, "_common.zig" });
const common_file = try out_dir.createFile(common_filename, .{});
defer common_file.close();
try protocol.emitCommon(common_file.writer());
try (try scanner.common.getOrPutValue(protocol_namespace, .{})).value.append(gpa, common_filename);
}
}
};
const Protocol = struct {
@@ -126,7 +249,7 @@ const Interface = struct {
return error.UnexpectedEndOfFile;
}
fn emit(interface: Interface, target: Target, namespace: []const u8, writer: anytype) !void {
fn emit(interface: Interface, side: Side, namespace: []const u8, writer: anytype) !void {
var buf: [1024]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buf);
try printIdentifier(fbs.writer(), case(.title, trimPrefix(interface.name)));
@@ -147,7 +270,7 @@ const Interface = struct {
try writer.writeAll(";\n");
}
if (target == .client) {
if (side == .client) {
if (interface.events.items.len > 0) {
try writer.writeAll("pub const Event = union(enum) {");
for (interface.events.items) |event| try event.emitField(.client, writer);
@@ -166,10 +289,10 @@ const Interface = struct {
}
for (interface.requests.items) |request, opcode|
try request.emitFn(target, writer, interface, opcode);
try request.emitFn(side, writer, interface, opcode);
if (mem.eql(u8, interface.name, "wl_display"))
try writer.writeAll(@embedFile("src/client_display_functions.zig"));
try writer.writeAll(@embedFile("client_display_functions.zig"));
} else {
if (interface.requests.items.len > 0) {
try writer.writeAll("pub const Request = union(enum) {");
@@ -196,7 +319,7 @@ const Interface = struct {
}
for (interface.events.items) |event, opcode|
try event.emitFn(target, writer, interface, opcode);
try event.emitFn(side, writer, interface, opcode);
}
try writer.writeAll("};\n");
@@ -327,15 +450,15 @@ const Message = struct {
try writer.writeAll("},");
}
fn emitField(message: Message, target: Target, writer: anytype) !void {
fn emitField(message: Message, side: Side, writer: anytype) !void {
try printIdentifier(writer, message.name);
try writer.writeAll(": struct {");
for (message.args.items) |arg| {
if (target == .server and arg.kind == .new_id and arg.kind.new_id == null) {
if (side == .server and arg.kind == .new_id and arg.kind.new_id == null) {
try writer.writeAll("interface: [*:0]const u8, version: u32,");
try printIdentifier(writer, arg.name);
try writer.writeAll(": u32");
} else if (target == .client and arg.kind == .new_id) {
} else if (side == .client and arg.kind == .new_id) {
try printIdentifier(writer, arg.name);
try writer.writeAll(": *");
try printAbsolute(.client, writer, arg.kind.new_id.?);
@@ -344,18 +467,18 @@ const Message = struct {
try printIdentifier(writer, arg.name);
try writer.writeByte(':');
// See notes on NULL in doc comment for wl_message in wayland-util.h
if (target == .client and arg.kind == .object and !arg.allow_null)
if (side == .client and arg.kind == .object and !arg.allow_null)
try writer.writeByte('?');
try arg.emitType(target, writer);
try arg.emitType(side, writer);
}
try writer.writeByte(',');
}
try writer.writeAll("},\n");
}
fn emitFn(message: Message, target: Target, writer: anytype, interface: Interface, opcode: usize) !void {
fn emitFn(message: Message, side: Side, writer: anytype, interface: Interface, opcode: usize) !void {
try writer.writeAll("pub fn ");
if (target == .server) {
if (side == .server) {
try writer.writeAll("send");
try printIdentifier(writer, case(.title, message.name));
} else {
@@ -366,7 +489,7 @@ const Message = struct {
try writer.writeAll(": *");
try printIdentifier(writer, case(.title, trimPrefix(interface.name)));
for (message.args.items) |arg| {
if (target == .server and arg.kind == .new_id) {
if (side == .server and arg.kind == .new_id) {
try writer.writeByte(',');
try printIdentifier(writer, arg.name);
try writer.writeByte(':');
@@ -376,16 +499,16 @@ const Message = struct {
try printIdentifier(writer, case(.title, trimPrefix(iface)))
else
try writer.writeAll("server.wl.Resource");
} else if (target == .client and arg.kind == .new_id) {
} else if (side == .client and arg.kind == .new_id) {
if (arg.kind.new_id == null) try writer.writeAll(", comptime T: type, version: u32");
} else {
try writer.writeByte(',');
try printIdentifier(writer, arg.name);
try writer.writeByte(':');
try arg.emitType(target, writer);
try arg.emitType(side, writer);
}
}
if (target == .server or message.kind != .constructor) {
if (side == .server or message.kind != .constructor) {
try writer.writeAll(") void {");
} else if (message.kind.constructor) |new_iface| {
try writer.writeAll(") !*");
@@ -394,7 +517,7 @@ const Message = struct {
} else {
try writer.writeAll(") !*T {");
}
if (target == .server)
if (side == .server)
try writer.writeAll("const resource = @ptrCast(*server.wl.Resource,")
else
try writer.writeAll("const proxy = @ptrCast(*client.wl.Proxy,");
@@ -410,7 +533,7 @@ const Message = struct {
try writer.writeAll(" = ");
if (arg.enum_name != null) {
try writer.writeAll("switch (@typeInfo(");
try arg.emitType(target, writer);
try arg.emitType(side, writer);
// TODO We know the type of the enum at scanning time, but it's
// currently a bit difficult to access it.
@@ -428,7 +551,7 @@ const Message = struct {
try writer.writeAll("},");
},
.object, .new_id => |new_iface| {
if (arg.kind == .object or target == .server) {
if (arg.kind == .object or side == .server) {
if (arg.allow_null) {
try writer.writeAll(".{ .o = if (");
try printIdentifier(writer, arg.name);
@@ -453,7 +576,7 @@ const Message = struct {
try writer.writeAll("};\n");
}
const args = if (message.args.items.len > 0) "&args" else "null";
if (target == .server) {
if (side == .server) {
try writer.print("resource.postEvent({}, {});", .{ opcode, args });
} else switch (message.kind) {
.normal, .destructor => {
@@ -558,7 +681,7 @@ const Arg = struct {
}
}
fn emitType(arg: Arg, target: Target, writer: anytype) !void {
fn emitType(arg: Arg, side: Side, writer: anytype) !void {
switch (arg.kind) {
.int, .uint => {
if (arg.enum_name) |name| {
@@ -587,7 +710,7 @@ const Arg = struct {
},
.object => |interface| if (interface) |i| {
if (arg.allow_null) try writer.writeAll("?*") else try writer.writeByte('*');
try printAbsolute(target, writer, i);
try printAbsolute(side, writer, i);
} else {
if (arg.allow_null) try writer.writeByte('?');
try writer.writeAll("*common.Object");
@@ -717,104 +840,6 @@ const Entry = struct {
}
};
const Scanner = struct {
/// Map from namespace to list of generated files
const Map = std.hash_map.StringHashMap(std.ArrayListUnmanaged([]const u8));
client: Map = Map.init(gpa),
server: Map = Map.init(gpa),
common: Map = Map.init(gpa),
fn scanProtocol(scanner: *Scanner, xml_filename: []const u8) !void {
const xml_file = try std.fs.cwd().openFile(xml_filename, .{});
defer xml_file.close();
var arena = std.heap.ArenaAllocator.init(gpa);
defer arena.deinit();
const xml_bytes = try xml_file.readToEndAlloc(&arena.allocator, 512 * 4096);
const protocol = try Protocol.parseXML(&arena.allocator, xml_bytes);
const xml_basename = std.fs.path.basename(xml_filename);
const protocol_name = try gpa.dupe(u8, xml_basename[0 .. xml_basename.len - 4]);
for (protocol_name) |*ch| {
if (ch.* == '-') ch.* = '_';
}
const protocol_namespace = try gpa.dupe(u8, protocol.namespace);
const client_filename = try mem.concat(gpa, u8, &[_][]const u8{ protocol_name, "_client.zig" });
const client_file = try std.fs.cwd().createFile(client_filename, .{});
defer client_file.close();
try protocol.emitClient(client_file.writer());
try (try scanner.client.getOrPutValue(protocol_namespace, .{})).value.append(gpa, client_filename);
const server_filename = try mem.concat(gpa, u8, &[_][]const u8{ protocol_name, "_server.zig" });
const server_file = try std.fs.cwd().createFile(server_filename, .{});
defer server_file.close();
try protocol.emitServer(server_file.writer());
try (try scanner.server.getOrPutValue(protocol_namespace, .{})).value.append(gpa, server_filename);
const common_filename = try mem.concat(gpa, u8, &[_][]const u8{ protocol_name, "_common.zig" });
const common_file = try std.fs.cwd().createFile(common_filename, .{});
defer common_file.close();
try protocol.emitCommon(common_file.writer());
try (try scanner.common.getOrPutValue(protocol_namespace, .{})).value.append(gpa, common_filename);
}
};
pub fn main() !void {
var scanner = Scanner{};
const argv = std.os.argv;
for (argv[1..]) |xml_filename|
try scanner.scanProtocol(mem.span(xml_filename));
{
const client_file = try std.fs.cwd().createFile("client.zig", .{});
defer client_file.close();
const writer = client_file.writer();
var iter = scanner.client.iterator();
while (iter.next()) |entry| {
try writer.print("pub const {} = struct {{", .{entry.key});
if (mem.eql(u8, entry.key, "wl"))
try writer.writeAll("pub usingnamespace @import(\"wayland_client_core.zig\");\n");
for (entry.value.items) |generated_file|
try writer.print("pub usingnamespace @import(\"{}\");", .{generated_file});
try writer.writeAll("};\n");
}
}
{
const server_file = try std.fs.cwd().createFile("server.zig", .{});
defer server_file.close();
const writer = server_file.writer();
var iter = scanner.server.iterator();
while (iter.next()) |entry| {
try writer.print("pub const {} = struct {{", .{entry.key});
if (mem.eql(u8, entry.key, "wl"))
try writer.writeAll("pub usingnamespace @import(\"wayland_server_core.zig\");\n");
for (entry.value.items) |generated_file|
try writer.print("pub usingnamespace @import(\"{}\");", .{generated_file});
try writer.writeAll("};\n");
}
}
{
const common_file = try std.fs.cwd().createFile("common.zig", .{});
defer common_file.close();
const writer = common_file.writer();
try writer.writeAll(@embedFile("src/common_core.zig"));
var iter = scanner.common.iterator();
while (iter.next()) |entry| {
try writer.print("pub const {} = struct {{", .{entry.key});
for (entry.value.items) |generated_file|
try writer.print("pub usingnamespace @import(\"{}\");", .{generated_file});
try writer.writeAll("};\n");
}
}
}
fn prefix(s: []const u8) []const u8 {
return s[0..mem.indexOfScalar(u8, s, '_').?];
}
@@ -839,8 +864,8 @@ fn case(out_case: enum { title, camel }, snake_case: []const u8) []const u8 {
return case_buf[0..i];
}
fn printAbsolute(target: Target, writer: anytype, interface: []const u8) !void {
try writer.writeAll(@tagName(target));
fn printAbsolute(side: Side, writer: anytype, interface: []const u8) !void {
try writer.writeAll(@tagName(side));
try writer.writeByte('.');
try printIdentifier(writer, prefix(interface));
try writer.writeByte('.');
@@ -870,7 +895,7 @@ test "parsing" {
var arena = std.heap.ArenaAllocator.init(testing.allocator);
defer arena.deinit();
const protocol = try Protocol.parseXML(&arena.allocator, @embedFile("protocol/wayland.xml"));
const protocol = try Protocol.parseXML(&arena.allocator, @embedFile("../protocol/wayland.xml"));
testing.expectEqualSlices(u8, "wayland", protocol.name);
testing.expectEqual(@as(usize, 22), protocol.interfaces.items.len);
View File