diff --git a/src/Compilation.zig b/src/Compilation.zig index 177746ae90..e23b6a12e4 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -3307,7 +3307,7 @@ pub fn addCCArgs( try argv.appendSlice(&[_][]const u8{ "-target", llvm_triple }); switch (ext) { - .c, .cpp, .m, .h => { + .c, .cpp, .m, .mm, .h => { try argv.appendSlice(&[_][]const u8{ "-nostdinc", "-fno-spell-checking", @@ -3316,6 +3316,10 @@ pub fn addCCArgs( try argv.append("-flto"); } + if (ext == .mm) { + try argv.append("-ObjC++"); + } + // According to Rich Felker libc headers are supposed to go before C language headers. // However as noted by @dimenus, appending libc headers before c_headers breaks intrinsics // and other compiler specific items. @@ -3599,6 +3603,7 @@ pub const FileExt = enum { cpp, h, m, + mm, ll, bc, assembly, @@ -3610,7 +3615,7 @@ pub const FileExt = enum { pub fn clangSupportsDepFile(ext: FileExt) bool { return switch (ext) { - .c, .cpp, .h, .m => true, + .c, .cpp, .h, .m, .mm => true, .ll, .bc, @@ -3648,6 +3653,10 @@ pub fn hasObjCExt(filename: []const u8) bool { return mem.endsWith(u8, filename, ".m"); } +pub fn hasObjCppExt(filename: []const u8) bool { + return mem.endsWith(u8, filename, ".mm"); +} + pub fn hasAsmExt(filename: []const u8) bool { return mem.endsWith(u8, filename, ".s") or mem.endsWith(u8, filename, ".S"); } @@ -3686,6 +3695,8 @@ pub fn classifyFileExt(filename: []const u8) FileExt { return .cpp; } else if (hasObjCExt(filename)) { return .m; + } else if (hasObjCppExt(filename)) { + return .mm; } else if (mem.endsWith(u8, filename, ".ll")) { return .ll; } else if (mem.endsWith(u8, filename, ".bc")) { @@ -3710,6 +3721,7 @@ pub fn classifyFileExt(filename: []const u8) FileExt { test "classifyFileExt" { try std.testing.expectEqual(FileExt.cpp, classifyFileExt("foo.cc")); try std.testing.expectEqual(FileExt.m, classifyFileExt("foo.m")); + try std.testing.expectEqual(FileExt.mm, classifyFileExt("foo.mm")); try std.testing.expectEqual(FileExt.unknown, classifyFileExt("foo.nim")); try std.testing.expectEqual(FileExt.shared_library, classifyFileExt("foo.so")); try std.testing.expectEqual(FileExt.shared_library, classifyFileExt("foo.so.1")); diff --git a/src/main.zig b/src/main.zig index 3479e1cac3..453124c55e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -296,6 +296,7 @@ const usage_build_generic = \\ .c C source code (requires LLVM extensions) \\ .cxx .cc .C .cpp C++ source code (requires LLVM extensions) \\ .m Objective-C source code (requires LLVM extensions) + \\ .mm Objective-C++ source code (requires LLVM extensions) \\ .bc LLVM IR Module (requires LLVM extensions) \\ \\General Options: @@ -1190,7 +1191,7 @@ fn buildOutputType( .object, .static_library, .shared_library => { try link_objects.append(arg); }, - .assembly, .c, .cpp, .h, .ll, .bc, .m => { + .assembly, .c, .cpp, .h, .ll, .bc, .m, .mm => { try c_source_files.append(.{ .src_path = arg, .extra_flags = try arena.dupe([]const u8, extra_cflags.items), @@ -1256,7 +1257,7 @@ fn buildOutputType( .positional => { const file_ext = Compilation.classifyFileExt(mem.spanZ(it.only_arg)); switch (file_ext) { - .assembly, .c, .cpp, .ll, .bc, .h, .m => try c_source_files.append(.{ .src_path = it.only_arg }), + .assembly, .c, .cpp, .ll, .bc, .h, .m, .mm => try c_source_files.append(.{ .src_path = it.only_arg }), .unknown, .shared_library, .object, .static_library => { try link_objects.append(it.only_arg); }, diff --git a/test/standalone.zig b/test/standalone.zig index 35d30fafca..762e8a9b58 100644 --- a/test/standalone.zig +++ b/test/standalone.zig @@ -69,6 +69,11 @@ pub fn addCases(cases: *tests.StandaloneContext) void { .build_modes = true, .requires_macos_sdk = true, }); + // Try to build and run an Objective-C++ executable. + cases.addBuildFile("test/standalone/objcpp/build.zig", .{ + .build_modes = true, + .requires_macos_sdk = true, + }); // Ensure the development tools are buildable. cases.add("tools/gen_spirv_spec.zig"); diff --git a/test/standalone/objcpp/Foo.h b/test/standalone/objcpp/Foo.h new file mode 100644 index 0000000000..05cb7df39b --- /dev/null +++ b/test/standalone/objcpp/Foo.h @@ -0,0 +1,7 @@ +#import + +@interface Foo : NSObject + +- (NSString *)name; + +@end diff --git a/test/standalone/objcpp/Foo.mm b/test/standalone/objcpp/Foo.mm new file mode 100644 index 0000000000..6fc9b1edf0 --- /dev/null +++ b/test/standalone/objcpp/Foo.mm @@ -0,0 +1,11 @@ +#import "Foo.h" + +@implementation Foo + +- (NSString *)name +{ + NSString *str = [[NSString alloc] initWithFormat:@"Zig"]; + return str; +} + +@end diff --git a/test/standalone/objcpp/build.zig b/test/standalone/objcpp/build.zig new file mode 100644 index 0000000000..a38f7b4c9a --- /dev/null +++ b/test/standalone/objcpp/build.zig @@ -0,0 +1,36 @@ +const std = @import("std"); +const Builder = std.build.Builder; +const CrossTarget = std.zig.CrossTarget; + +fn isRunnableTarget(t: CrossTarget) bool { + // TODO I think we might be able to run this on Linux via Darling. + // Add a check for that here, and return true if Darling is available. + if (t.isNative() and t.getOsTag() == .macos) + return true + else + return false; +} + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + const target = b.standardTargetOptions(.{}); + + const test_step = b.step("test", "Test the program"); + + const exe = b.addExecutable("test", null); + b.default_step.dependOn(&exe.step); + exe.addIncludeDir("."); + exe.addCSourceFile("Foo.mm", &[0][]const u8{}); + exe.addCSourceFile("test.mm", &[0][]const u8{}); + exe.setBuildMode(mode); + exe.setTarget(target); + exe.linkLibCpp(); + // TODO when we figure out how to ship framework stubs for cross-compilation, + // populate paths to the sysroot here. + exe.linkFramework("Foundation"); + + if (isRunnableTarget(target)) { + const run_cmd = exe.run(); + test_step.dependOn(&run_cmd.step); + } +} diff --git a/test/standalone/objcpp/test.mm b/test/standalone/objcpp/test.mm new file mode 100644 index 0000000000..d27c543cdf --- /dev/null +++ b/test/standalone/objcpp/test.mm @@ -0,0 +1,14 @@ +#import "Foo.h" +#import +#include + +int main(int argc, char *argv[]) +{ + @autoreleasepool { + Foo *foo = [[Foo alloc] init]; + NSString *result = [foo name]; + std::cout << "Hello from C++ and " << [result UTF8String]; + assert([result isEqualToString:@"Zig"]); + return 0; + } +}