authorgravatar for stephen@hexops.comStephen Gutekanst <stephen@hexops.com> 2021-11-22 00:44:49-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-11-22 08:44:49+01:00
log9836f1b2f975cc484499847225eeb0ad54116942
treebb8bf2340fc4045f571d323d56e13c48d0524ef5
parent722c6b95671fcda0da1561205e487d169c046473
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

add support for compiling Objective-C++ code (#10096)

* add support for compiling Objective-C++ code Prior to this change, calling `step.addCSourceFiles` with Obj-C++ file extensions (`.mm`) would result in an error due to Zig not being aware of that extension. Clang supports an `-ObjC++` compilation mode flag, but it was only possible to use if you violated standards and renamed your `.mm` Obj-C++ files to `.m` (Obj-C) to workaround Zig being unaware of the extension. This change makes Zig aware of `.mm` files so they can be compiled, enabling compilation of projects such as [Google's Dawn WebGPU](https://dawn.googlesource.com/dawn/) using a `build.zig` file only. Helps hexops/mach#21 Signed-off-by: Stephen Gutekanst <stephen@hexops.com> * test/standalone: add ObjC++ compilation/linking test Based on the existing objc example, just tweaked for ObjC++. Signed-off-by: Stephen Gutekanst <stephen@hexops.com>

7 files changed, 90 insertions(+), 4 deletions(-)

src/Compilation.zig+14-2
...@@ -3307,7 +3307,7 @@ pub fn addCCArgs(...@@ -3307,7 +3307,7 @@ pub fn addCCArgs(
3307 try argv.appendSlice(&[_][]const u8{ "-target", llvm_triple });3307 try argv.appendSlice(&[_][]const u8{ "-target", llvm_triple });
33083308
3309 switch (ext) {3309 switch (ext) {
3310 .c, .cpp, .m, .h => {3310 .c, .cpp, .m, .mm, .h => {
3311 try argv.appendSlice(&[_][]const u8{3311 try argv.appendSlice(&[_][]const u8{
3312 "-nostdinc",3312 "-nostdinc",
3313 "-fno-spell-checking",3313 "-fno-spell-checking",
...@@ -3316,6 +3316,10 @@ pub fn addCCArgs(...@@ -3316,6 +3316,10 @@ pub fn addCCArgs(
3316 try argv.append("-flto");3316 try argv.append("-flto");
3317 }3317 }
33183318
3319 if (ext == .mm) {
3320 try argv.append("-ObjC++");
3321 }
3322
3319 // According to Rich Felker libc headers are supposed to go before C language headers.3323 // According to Rich Felker libc headers are supposed to go before C language headers.
3320 // However as noted by @dimenus, appending libc headers before c_headers breaks intrinsics3324 // However as noted by @dimenus, appending libc headers before c_headers breaks intrinsics
3321 // and other compiler specific items.3325 // and other compiler specific items.
...@@ -3599,6 +3603,7 @@ pub const FileExt = enum {...@@ -3599,6 +3603,7 @@ pub const FileExt = enum {
3599 cpp,3603 cpp,
3600 h,3604 h,
3601 m,3605 m,
3606 mm,
3602 ll,3607 ll,
3603 bc,3608 bc,
3604 assembly,3609 assembly,
...@@ -3610,7 +3615,7 @@ pub const FileExt = enum {...@@ -3610,7 +3615,7 @@ pub const FileExt = enum {
36103615
3611 pub fn clangSupportsDepFile(ext: FileExt) bool {3616 pub fn clangSupportsDepFile(ext: FileExt) bool {
3612 return switch (ext) {3617 return switch (ext) {
3613 .c, .cpp, .h, .m => true,3618 .c, .cpp, .h, .m, .mm => true,
36143619
3615 .ll,3620 .ll,
3616 .bc,3621 .bc,
...@@ -3648,6 +3653,10 @@ pub fn hasObjCExt(filename: []const u8) bool {...@@ -3648,6 +3653,10 @@ pub fn hasObjCExt(filename: []const u8) bool {
3648 return mem.endsWith(u8, filename, ".m");3653 return mem.endsWith(u8, filename, ".m");
3649}3654}
36503655
3656pub fn hasObjCppExt(filename: []const u8) bool {
3657 return mem.endsWith(u8, filename, ".mm");
3658}
3659
3651pub fn hasAsmExt(filename: []const u8) bool {3660pub fn hasAsmExt(filename: []const u8) bool {
3652 return mem.endsWith(u8, filename, ".s") or mem.endsWith(u8, filename, ".S");3661 return mem.endsWith(u8, filename, ".s") or mem.endsWith(u8, filename, ".S");
3653}3662}
...@@ -3686,6 +3695,8 @@ pub fn classifyFileExt(filename: []const u8) FileExt {...@@ -3686,6 +3695,8 @@ pub fn classifyFileExt(filename: []const u8) FileExt {
3686 return .cpp;3695 return .cpp;
3687 } else if (hasObjCExt(filename)) {3696 } else if (hasObjCExt(filename)) {
3688 return .m;3697 return .m;
3698 } else if (hasObjCppExt(filename)) {
3699 return .mm;
3689 } else if (mem.endsWith(u8, filename, ".ll")) {3700 } else if (mem.endsWith(u8, filename, ".ll")) {
3690 return .ll;3701 return .ll;
3691 } else if (mem.endsWith(u8, filename, ".bc")) {3702 } else if (mem.endsWith(u8, filename, ".bc")) {
...@@ -3710,6 +3721,7 @@ pub fn classifyFileExt(filename: []const u8) FileExt {...@@ -3710,6 +3721,7 @@ pub fn classifyFileExt(filename: []const u8) FileExt {
3710test "classifyFileExt" {3721test "classifyFileExt" {
3711 try std.testing.expectEqual(FileExt.cpp, classifyFileExt("foo.cc"));3722 try std.testing.expectEqual(FileExt.cpp, classifyFileExt("foo.cc"));
3712 try std.testing.expectEqual(FileExt.m, classifyFileExt("foo.m"));3723 try std.testing.expectEqual(FileExt.m, classifyFileExt("foo.m"));
3724 try std.testing.expectEqual(FileExt.mm, classifyFileExt("foo.mm"));
3713 try std.testing.expectEqual(FileExt.unknown, classifyFileExt("foo.nim"));3725 try std.testing.expectEqual(FileExt.unknown, classifyFileExt("foo.nim"));
3714 try std.testing.expectEqual(FileExt.shared_library, classifyFileExt("foo.so"));3726 try std.testing.expectEqual(FileExt.shared_library, classifyFileExt("foo.so"));
3715 try std.testing.expectEqual(FileExt.shared_library, classifyFileExt("foo.so.1"));3727 try std.testing.expectEqual(FileExt.shared_library, classifyFileExt("foo.so.1"));
src/main.zig+3-2
...@@ -296,6 +296,7 @@ const usage_build_generic =...@@ -296,6 +296,7 @@ const usage_build_generic =
296 \\ .c C source code (requires LLVM extensions)296 \\ .c C source code (requires LLVM extensions)
297 \\ .cxx .cc .C .cpp C++ source code (requires LLVM extensions)297 \\ .cxx .cc .C .cpp C++ source code (requires LLVM extensions)
298 \\ .m Objective-C source code (requires LLVM extensions)298 \\ .m Objective-C source code (requires LLVM extensions)
299 \\ .mm Objective-C++ source code (requires LLVM extensions)
299 \\ .bc LLVM IR Module (requires LLVM extensions)300 \\ .bc LLVM IR Module (requires LLVM extensions)
300 \\301 \\
301 \\General Options:302 \\General Options:
...@@ -1190,7 +1191,7 @@ fn buildOutputType(...@@ -1190,7 +1191,7 @@ fn buildOutputType(
1190 .object, .static_library, .shared_library => {1191 .object, .static_library, .shared_library => {
1191 try link_objects.append(arg);1192 try link_objects.append(arg);
1192 },1193 },
1193 .assembly, .c, .cpp, .h, .ll, .bc, .m => {1194 .assembly, .c, .cpp, .h, .ll, .bc, .m, .mm => {
1194 try c_source_files.append(.{1195 try c_source_files.append(.{
1195 .src_path = arg,1196 .src_path = arg,
1196 .extra_flags = try arena.dupe([]const u8, extra_cflags.items),1197 .extra_flags = try arena.dupe([]const u8, extra_cflags.items),
...@@ -1256,7 +1257,7 @@ fn buildOutputType(...@@ -1256,7 +1257,7 @@ fn buildOutputType(
1256 .positional => {1257 .positional => {
1257 const file_ext = Compilation.classifyFileExt(mem.spanZ(it.only_arg));1258 const file_ext = Compilation.classifyFileExt(mem.spanZ(it.only_arg));
1258 switch (file_ext) {1259 switch (file_ext) {
1259 .assembly, .c, .cpp, .ll, .bc, .h, .m => try c_source_files.append(.{ .src_path = it.only_arg }),1260 .assembly, .c, .cpp, .ll, .bc, .h, .m, .mm => try c_source_files.append(.{ .src_path = it.only_arg }),
1260 .unknown, .shared_library, .object, .static_library => {1261 .unknown, .shared_library, .object, .static_library => {
1261 try link_objects.append(it.only_arg);1262 try link_objects.append(it.only_arg);
1262 },1263 },
test/standalone.zig+5
...@@ -69,6 +69,11 @@ pub fn addCases(cases: *tests.StandaloneContext) void {...@@ -69,6 +69,11 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
69 .build_modes = true,69 .build_modes = true,
70 .requires_macos_sdk = true,70 .requires_macos_sdk = true,
71 });71 });
72 // Try to build and run an Objective-C++ executable.
73 cases.addBuildFile("test/standalone/objcpp/build.zig", .{
74 .build_modes = true,
75 .requires_macos_sdk = true,
76 });
7277
73 // Ensure the development tools are buildable.78 // Ensure the development tools are buildable.
74 cases.add("tools/gen_spirv_spec.zig");79 cases.add("tools/gen_spirv_spec.zig");
test/standalone/objcpp/Foo.h created+7
...@@ -0,0 +1,7 @@
1#import <Foundation/Foundation.h>
2
3@interface Foo : NSObject
4
5- (NSString *)name;
6
7@end
test/standalone/objcpp/Foo.mm created+11
...@@ -0,0 +1,11 @@
1#import "Foo.h"
2
3@implementation Foo
4
5- (NSString *)name
6{
7 NSString *str = [[NSString alloc] initWithFormat:@"Zig"];
8 return str;
9}
10
11@end
test/standalone/objcpp/build.zig created+36
...@@ -0,0 +1,36 @@
1const std = @import("std");
2const Builder = std.build.Builder;
3const CrossTarget = std.zig.CrossTarget;
4
5fn isRunnableTarget(t: CrossTarget) bool {
6 // TODO I think we might be able to run this on Linux via Darling.
7 // Add a check for that here, and return true if Darling is available.
8 if (t.isNative() and t.getOsTag() == .macos)
9 return true
10 else
11 return false;
12}
13
14pub fn build(b: *Builder) void {
15 const mode = b.standardReleaseOptions();
16 const target = b.standardTargetOptions(.{});
17
18 const test_step = b.step("test", "Test the program");
19
20 const exe = b.addExecutable("test", null);
21 b.default_step.dependOn(&exe.step);
22 exe.addIncludeDir(".");
23 exe.addCSourceFile("Foo.mm", &[0][]const u8{});
24 exe.addCSourceFile("test.mm", &[0][]const u8{});
25 exe.setBuildMode(mode);
26 exe.setTarget(target);
27 exe.linkLibCpp();
28 // TODO when we figure out how to ship framework stubs for cross-compilation,
29 // populate paths to the sysroot here.
30 exe.linkFramework("Foundation");
31
32 if (isRunnableTarget(target)) {
33 const run_cmd = exe.run();
34 test_step.dependOn(&run_cmd.step);
35 }
36}
test/standalone/objcpp/test.mm created+14
...@@ -0,0 +1,14 @@
1#import "Foo.h"
2#import <assert.h>
3#include <iostream>
4
5int main(int argc, char *argv[])
6{
7 @autoreleasepool {
8 Foo *foo = [[Foo alloc] init];
9 NSString *result = [foo name];
10 std::cout << "Hello from C++ and " << [result UTF8String];
11 assert([result isEqualToString:@"Zig"]);
12 return 0;
13 }
14}