diff --git a/.gitmodules b/.gitmodules index 4b66a803a..2ae7ddc86 100644 --- a/.gitmodules +++ b/.gitmodules @@ -37,3 +37,6 @@ [submodule "vendor/zig-js"] path = vendor/zig-js url = https://github.com/mitchellh/zig-js.git +[submodule "vendor/zig-objc"] + path = vendor/zig-objc + url = https://github.com/mitchellh/zig-objc.git diff --git a/build.zig b/build.zig index 81f47f88d..e14e97ed3 100644 --- a/build.zig +++ b/build.zig @@ -12,7 +12,7 @@ const libxml2 = @import("vendor/zig-libxml2/libxml2.zig"); const libuv = @import("pkg/libuv/build.zig"); const libpng = @import("pkg/libpng/build.zig"); const macos = @import("pkg/macos/build.zig"); -const objc = @import("pkg/objc/build.zig"); +const objc = @import("vendor/zig-objc/build.zig"); const pixman = @import("pkg/pixman/build.zig"); const stb_image_resize = @import("pkg/stb_image_resize/build.zig"); const utf8proc = @import("pkg/utf8proc/build.zig"); diff --git a/pkg/objc/autorelease.zig b/pkg/objc/autorelease.zig deleted file mode 100644 index 18adf1433..000000000 --- a/pkg/objc/autorelease.zig +++ /dev/null @@ -1,16 +0,0 @@ -const std = @import("std"); - -pub const AutoreleasePool = opaque { - pub inline fn init() *AutoreleasePool { - return @ptrCast(*AutoreleasePool, objc_autoreleasePoolPush().?); - } - - pub inline fn deinit(self: *AutoreleasePool) void { - objc_autoreleasePoolPop(self); - } -}; - -// I'm not sure if these are internal or not... they aren't in any headers, -// but its how autorelease pools are implemented. -extern "c" fn objc_autoreleasePoolPush() ?*anyopaque; -extern "c" fn objc_autoreleasePoolPop(?*anyopaque) void; diff --git a/pkg/objc/build.zig b/pkg/objc/build.zig deleted file mode 100644 index a29d90daf..000000000 --- a/pkg/objc/build.zig +++ /dev/null @@ -1,10 +0,0 @@ -const std = @import("std"); - -pub const pkg = std.build.Pkg{ - .name = "objc", - .source = .{ .path = thisDir() ++ "/main.zig" }, -}; - -fn thisDir() []const u8 { - return std.fs.path.dirname(@src().file) orelse "."; -} diff --git a/pkg/objc/c.zig b/pkg/objc/c.zig deleted file mode 100644 index 622ea4bce..000000000 --- a/pkg/objc/c.zig +++ /dev/null @@ -1,4 +0,0 @@ -pub usingnamespace @cImport({ - @cInclude("objc/runtime.h"); - @cInclude("objc/message.h"); -}); diff --git a/pkg/objc/class.zig b/pkg/objc/class.zig deleted file mode 100644 index c6a310307..000000000 --- a/pkg/objc/class.zig +++ /dev/null @@ -1,74 +0,0 @@ -const std = @import("std"); -const c = @import("c.zig"); -const objc = @import("main.zig"); -const MsgSend = @import("msg_send.zig").MsgSend; - -pub const Class = struct { - value: c.Class, - - pub usingnamespace MsgSend(Class); - - /// Returns the class definition of a specified class. - pub fn getClass(name: [:0]const u8) ?Class { - return Class{ - .value = c.objc_getClass(name.ptr) orelse return null, - }; - } - - /// Returns a property with a given name of a given class. - pub fn getProperty(self: Class, name: [:0]const u8) ?objc.Property { - return objc.Property{ - .value = c.class_getProperty(self.value, name.ptr) orelse return null, - }; - } - - /// Describes the properties declared by a class. This must be freed. - pub fn copyPropertyList(self: Class) []objc.Property { - var count: c_uint = undefined; - const list = @ptrCast([*c]objc.Property, c.class_copyPropertyList(self.value, &count)); - if (count == 0) return list[0..0]; - return list[0..count]; - } -}; - -test "getClass" { - const testing = std.testing; - const NSObject = Class.getClass("NSObject"); - try testing.expect(NSObject != null); - try testing.expect(Class.getClass("NoWay") == null); -} - -test "msgSend" { - const testing = std.testing; - const NSObject = Class.getClass("NSObject").?; - - // Should work with primitives - const id = NSObject.msgSend(c.id, objc.Sel.registerName("alloc"), .{}); - try testing.expect(id != null); - { - const obj: objc.Object = .{ .value = id }; - obj.msgSend(void, objc.sel("dealloc"), .{}); - } - - // Should work with our wrappers - const obj = NSObject.msgSend(objc.Object, objc.Sel.registerName("alloc"), .{}); - try testing.expect(obj.value != null); - obj.msgSend(void, objc.sel("dealloc"), .{}); -} - -test "getProperty" { - const testing = std.testing; - const NSObject = Class.getClass("NSObject").?; - - try testing.expect(NSObject.getProperty("className") != null); - try testing.expect(NSObject.getProperty("nope") == null); -} - -test "copyProperyList" { - const testing = std.testing; - const NSObject = Class.getClass("NSObject").?; - - const list = NSObject.copyPropertyList(); - defer objc.free(list); - try testing.expect(list.len > 20); -} diff --git a/pkg/objc/main.zig b/pkg/objc/main.zig deleted file mode 100644 index fe18b06ed..000000000 --- a/pkg/objc/main.zig +++ /dev/null @@ -1,18 +0,0 @@ -const std = @import("std"); - -pub const c = @import("c.zig"); -pub usingnamespace @import("autorelease.zig"); -pub usingnamespace @import("class.zig"); -pub usingnamespace @import("object.zig"); -pub usingnamespace @import("property.zig"); -pub usingnamespace @import("sel.zig"); - -/// This just calls the C allocator free. Some things need to be freed -/// and this is how they can be freed for objc. -pub inline fn free(ptr: anytype) void { - std.heap.c_allocator.free(ptr); -} - -test { - std.testing.refAllDecls(@This()); -} diff --git a/pkg/objc/msg_send.zig b/pkg/objc/msg_send.zig deleted file mode 100644 index ce0d049b2..000000000 --- a/pkg/objc/msg_send.zig +++ /dev/null @@ -1,177 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const assert = std.debug.assert; -const c = @import("c.zig"); -const objc = @import("main.zig"); - -/// Returns a struct that implements the msgSend function for type T. -/// This is meant to be used with `usingnamespace` to add dispatch -/// capability to a type that supports it. -pub fn MsgSend(comptime T: type) type { - // 1. T should be a struct - // 2. T should have a field "value" that can be an "id" (same size) - - return struct { - /// Invoke a selector on the target, i.e. an instance method on an - /// object or a class method on a class. The args should be a tuple. - pub fn msgSend( - target: T, - comptime Return: type, - sel: objc.Sel, - args: anytype, - ) Return { - // Our one special-case: If the return type is our own Object - // type then we wrap it. - const is_object = Return == objc.Object; - - // Our actual return value is an "id" if we are using one of - // our built-in types (see above). Otherwise, we trust the caller. - const RealReturn = if (is_object) c.id else Return; - - // See objc/message.h. The high-level is that depending on the - // target architecture and return type, we must use a different - // objc_msgSend function. - const msg_send_fn = switch (builtin.target.cpu.arch) { - // Aarch64 uses objc_msgSend for everything. Hurray! - .aarch64 => &c.objc_msgSend, - - // x86_64 depends on the return type... - .x86_64 => switch (@typeInfo(RealReturn)) { - // Most types use objc_msgSend - inline .Int, .Bool, .Pointer, .Void => &c.objc_msgSend, - .Optional => |opt| opt: { - assert(@typeInfo(opt.child) == .Pointer); - break :opt &c.objc_msgSend; - }, - - // Structs must use objc_msgSend_stret. - // NOTE: This is probably WAY more complicated... we only - // call this if the struct is NOT returned as a register. - // And that depends on the size of the struct. But I don't - // know what the breakpoint actually is for that. This SO - // answer says 16 bytes so I'm going to use that but I have - // no idea... - .Struct => if (@sizeOf(Return) > 16) - &c.objc_msgSend_stret - else - &c.objc_msgSend, - - // Floats use objc_msgSend_fpret for f64 on x86_64, - // but normal msgSend for other bit sizes. i386 has - // more complex rules but we don't support i386 at the time - // of this comment and probably never will since all i386 - // Apple models are discontinued at this point. - .Float => |float| switch (float.bits) { - 64 => &c.objc_msgSend_fpret, - else => &c.objc_msgSend, - }, - - // Otherwise we log in case we need to add a new case above - else => { - @compileLog(@typeInfo(RealReturn)); - @compileError("unsupported return type for objc runtime on x86_64"); - }, - }, - else => @compileError("unsupported objc architecture"), - }; - - // Build our function type and call it - const Fn = MsgSendFn(RealReturn, @TypeOf(target.value), @TypeOf(args)); - // Due to this stage2 Zig issue[1], this must be var for now. - // [1]: https://github.com/ziglang/zig/issues/13598 - var msg_send_ptr = @ptrCast(*const Fn, msg_send_fn); - const result = @call(.auto, msg_send_ptr, .{ target.value, sel.value } ++ args); - - if (!is_object) return result; - return .{ .value = result }; - } - }; -} - -/// This returns a function body type for `obj_msgSend` that matches -/// the given return type, target type, and arguments tuple type. -/// -/// obj_msgSend is a really interesting function, because it doesn't act -/// like a typical function. You have to call it with the C ABI as if you're -/// calling the true target function, not as a varargs C function. Therefore -/// you have to cast obj_msgSend to a function pointer type of the final -/// destination function, then call that. -/// -/// Example: you have an ObjC function like this: -/// -/// @implementation Foo -/// - (void)log: (float)x { /* stuff */ } -/// -/// If you call it like this, it won't work (you'll get garbage): -/// -/// objc_msgSend(obj, @selector(log:), (float)PI); -/// -/// You have to call it like this: -/// -/// ((void (*)(id, SEL, float))objc_msgSend)(obj, @selector(log:), M_PI); -/// -/// This comptime function returns the function body type that can be used -/// to cast and call for the proper C ABI behavior. -fn MsgSendFn( - comptime Return: type, - comptime Target: type, - comptime Args: type, -) type { - const argsInfo = @typeInfo(Args).Struct; - assert(argsInfo.is_tuple); - - // Target must always be an "id". Lots of types (Class, Object, etc.) - // are an "id" so we just make sure the sizes match for ABI reasons. - assert(@sizeOf(Target) == @sizeOf(c.id)); - - // Build up our argument types. - const Fn = std.builtin.Type.Fn; - const params: []Fn.Param = params: { - var acc: [argsInfo.fields.len + 2]Fn.Param = undefined; - - // First argument is always the target and selector. - acc[0] = .{ .type = Target, .is_generic = false, .is_noalias = false }; - acc[1] = .{ .type = c.SEL, .is_generic = false, .is_noalias = false }; - - // Remaining arguments depend on the args given, in the order given - for (argsInfo.fields) |field, i| { - acc[i + 2] = .{ - .type = field.type, - .is_generic = false, - .is_noalias = false, - }; - } - - break :params &acc; - }; - - // Copy the alignment of a normal function type so equality works - // (mainly for tests, I don't think this has any consequence otherwise) - const alignment = @typeInfo(fn () callconv(.C) void).Fn.alignment; - - return @Type(.{ - .Fn = .{ - .calling_convention = .C, - .alignment = alignment, - .is_generic = false, - .is_var_args = false, - .return_type = Return, - .params = params, - }, - }); -} - -test { - // https://github.com/ziglang/zig/issues/12360 - if (true) return error.SkipZigTest; - - const testing = std.testing; - try testing.expectEqual(fn ( - u8, - objc.Sel, - ) callconv(.C) u64, MsgSendFn(u64, u8, @TypeOf(.{}))); - try testing.expectEqual(fn (u8, objc.Sel, u16, u32) callconv(.C) u64, MsgSendFn(u64, u8, @TypeOf(.{ - @as(u16, 0), - @as(u32, 0), - }))); -} diff --git a/pkg/objc/object.zig b/pkg/objc/object.zig deleted file mode 100644 index b69b0ee22..000000000 --- a/pkg/objc/object.zig +++ /dev/null @@ -1,70 +0,0 @@ -const std = @import("std"); -const c = @import("c.zig"); -const objc = @import("main.zig"); -const MsgSend = @import("msg_send.zig").MsgSend; - -pub const Object = struct { - value: c.id, - - pub usingnamespace MsgSend(Object); - - pub fn fromId(id: anytype) Object { - return .{ .value = @ptrCast(c.id, @alignCast(@alignOf(c.id), id)) }; - } - - /// Returns the class of an object. - pub fn getClass(self: Object) ?objc.Class { - return objc.Class{ - .value = c.object_getClass(self.value) orelse return null, - }; - } - - /// Returns the class name of a given object. - pub fn getClassName(self: Object) [:0]const u8 { - return std.mem.sliceTo(c.object_getClassName(self.value), 0); - } - - /// Set a property. This is a helper around getProperty and is - /// strictly less performant than doing it manually. Consider doing - /// this manually if performance is critical. - pub fn setProperty(self: Object, comptime n: [:0]const u8, v: anytype) void { - const Class = self.getClass().?; - const prop = Class.getProperty(n).?; - const setter = if (prop.copyAttributeValue("S")) |val| setter: { - defer objc.free(val); - break :setter objc.sel(val); - } else objc.sel( - "set" ++ - [1]u8{std.ascii.toUpper(n[0])} ++ - n[1..n.len] ++ - ":", - ); - - self.msgSend(void, setter, .{v}); - } - - /// Get a property. This is a helper around Class.getProperty and is - /// strictly less performant than doing it manually. Consider doing - /// this manually if performance is critical. - pub fn getProperty(self: Object, comptime T: type, comptime n: [:0]const u8) T { - const Class = self.getClass().?; - const prop = Class.getProperty(n).?; - const getter = if (prop.copyAttributeValue("G")) |val| getter: { - defer objc.free(val); - break :getter objc.sel(val); - } else objc.sel(n); - - return self.msgSend(T, getter, .{}); - } -}; - -test { - const testing = std.testing; - const NSObject = objc.Class.getClass("NSObject").?; - - // Should work with our wrappers - const obj = NSObject.msgSend(objc.Object, objc.Sel.registerName("alloc"), .{}); - try testing.expect(obj.value != null); - try testing.expectEqualStrings("NSObject", obj.getClassName()); - obj.msgSend(void, objc.sel("dealloc"), .{}); -} diff --git a/pkg/objc/property.zig b/pkg/objc/property.zig deleted file mode 100644 index 9adcb3c20..000000000 --- a/pkg/objc/property.zig +++ /dev/null @@ -1,35 +0,0 @@ -const std = @import("std"); -const c = @import("c.zig"); -const objc = @import("main.zig"); - -pub const Property = extern struct { - value: c.objc_property_t, - - /// Returns the name of a property. - pub fn getName(self: Property) [:0]const u8 { - return std.mem.sliceTo(c.property_getName(self.value), 0); - } - - /// Returns the value of a property attribute given the attribute name. - pub fn copyAttributeValue(self: Property, attr: [:0]const u8) ?[:0]u8 { - return std.mem.sliceTo( - c.property_copyAttributeValue(self.value, attr.ptr) orelse return null, - 0, - ); - } -}; - -test { - // Critical properties because we ptrCast C pointers to this. - const testing = std.testing; - try testing.expect(@sizeOf(Property) == @sizeOf(c.objc_property_t)); - try testing.expect(@alignOf(Property) == @alignOf(c.objc_property_t)); -} - -test { - const testing = std.testing; - const NSObject = objc.Class.getClass("NSObject").?; - - const prop = NSObject.getProperty("className").?; - try testing.expectEqualStrings("className", prop.getName()); -} diff --git a/pkg/objc/sel.zig b/pkg/objc/sel.zig deleted file mode 100644 index 23c629f48..000000000 --- a/pkg/objc/sel.zig +++ /dev/null @@ -1,30 +0,0 @@ -const std = @import("std"); -const c = @import("c.zig"); - -// Shorthand, equivalent to Sel.registerName -pub inline fn sel(name: [:0]const u8) Sel { - return Sel.registerName(name); -} - -pub const Sel = struct { - value: c.SEL, - - /// Registers a method with the Objective-C runtime system, maps the - /// method name to a selector, and returns the selector value. - pub fn registerName(name: [:0]const u8) Sel { - return Sel{ - .value = c.sel_registerName(name.ptr), - }; - } - - /// Returns the name of the method specified by a given selector. - pub fn getName(self: Sel) [:0]const u8 { - return std.mem.sliceTo(c.sel_getName(self.value), 0); - } -}; - -test { - const testing = std.testing; - const s = Sel.registerName("yo"); - try testing.expectEqualStrings("yo", s.getName()); -} diff --git a/vendor/zig-objc b/vendor/zig-objc new file mode 160000 index 000000000..4f5eda096 --- /dev/null +++ b/vendor/zig-objc @@ -0,0 +1 @@ +Subproject commit 4f5eda096c327a6943c834762cead4d1c9db2366