| author | |
| committer | |
| log | 9836f1b2f975cc484499847225eeb0ad54116942 |
| tree | bb8bf2340fc4045f571d323d56e13c48d0524ef5 |
| parent | 722c6b95671fcda0da1561205e487d169c046473 |
| signature |
* 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 }); |
| 3308 | 3308 | ||
| 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 | } |
| 3318 | 3318 | ||
| 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 intrinsics | 3324 | // 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 { |
| 3610 | 3615 | ||
| 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, |
| 3614 | 3619 | ||
| 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 | } |
| 3650 | 3655 | ||
| 3656 | pub fn hasObjCppExt(filename: []const u8) bool { | ||
| 3657 | return mem.endsWith(u8, filename, ".mm"); | ||
| 3658 | } | ||
| 3659 | |||
| 3651 | pub fn hasAsmExt(filename: []const u8) bool { | 3660 | pub 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 { |
| 3710 | test "classifyFileExt" { | 3721 | test "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 | }); | ||
| 72 | 77 | ||
| 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const Builder = std.build.Builder; | ||
| 3 | const CrossTarget = std.zig.CrossTarget; | ||
| 4 | |||
| 5 | fn 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 | |||
| 14 | pub 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 | |||
| 5 | int 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 | } | ||