| 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 | 3307 | try argv.appendSlice(&[_][]const u8{ "-target", llvm_triple }); |
| 3308 | 3308 | |
| 3309 | 3309 | switch (ext) { |
| 3310 | .c, .cpp, .m, .h => { | |
| 3310 | .c, .cpp, .m, .mm, .h => { | |
| 3311 | 3311 | try argv.appendSlice(&[_][]const u8{ |
| 3312 | 3312 | "-nostdinc", |
| 3313 | 3313 | "-fno-spell-checking", |
| ... | ... | @@ -3316,6 +3316,10 @@ pub fn addCCArgs( |
| 3316 | 3316 | try argv.append("-flto"); |
| 3317 | 3317 | } |
| 3318 | 3318 | |
| 3319 | if (ext == .mm) { | |
| 3320 | try argv.append("-ObjC++"); | |
| 3321 | } | |
| 3322 | ||
| 3319 | 3323 | // According to Rich Felker libc headers are supposed to go before C language headers. |
| 3320 | 3324 | // However as noted by @dimenus, appending libc headers before c_headers breaks intrinsics |
| 3321 | 3325 | // and other compiler specific items. |
| ... | ... | @@ -3599,6 +3603,7 @@ pub const FileExt = enum { |
| 3599 | 3603 | cpp, |
| 3600 | 3604 | h, |
| 3601 | 3605 | m, |
| 3606 | mm, | |
| 3602 | 3607 | ll, |
| 3603 | 3608 | bc, |
| 3604 | 3609 | assembly, |
| ... | ... | @@ -3610,7 +3615,7 @@ pub const FileExt = enum { |
| 3610 | 3615 | |
| 3611 | 3616 | pub fn clangSupportsDepFile(ext: FileExt) bool { |
| 3612 | 3617 | return switch (ext) { |
| 3613 | .c, .cpp, .h, .m => true, | |
| 3618 | .c, .cpp, .h, .m, .mm => true, | |
| 3614 | 3619 | |
| 3615 | 3620 | .ll, |
| 3616 | 3621 | .bc, |
| ... | ... | @@ -3648,6 +3653,10 @@ pub fn hasObjCExt(filename: []const u8) bool { |
| 3648 | 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 | 3660 | pub fn hasAsmExt(filename: []const u8) bool { |
| 3652 | 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 | 3695 | return .cpp; |
| 3687 | 3696 | } else if (hasObjCExt(filename)) { |
| 3688 | 3697 | return .m; |
| 3698 | } else if (hasObjCppExt(filename)) { | |
| 3699 | return .mm; | |
| 3689 | 3700 | } else if (mem.endsWith(u8, filename, ".ll")) { |
| 3690 | 3701 | return .ll; |
| 3691 | 3702 | } else if (mem.endsWith(u8, filename, ".bc")) { |
| ... | ... | @@ -3710,6 +3721,7 @@ pub fn classifyFileExt(filename: []const u8) FileExt { |
| 3710 | 3721 | test "classifyFileExt" { |
| 3711 | 3722 | try std.testing.expectEqual(FileExt.cpp, classifyFileExt("foo.cc")); |
| 3712 | 3723 | try std.testing.expectEqual(FileExt.m, classifyFileExt("foo.m")); |
| 3724 | try std.testing.expectEqual(FileExt.mm, classifyFileExt("foo.mm")); | |
| 3713 | 3725 | try std.testing.expectEqual(FileExt.unknown, classifyFileExt("foo.nim")); |
| 3714 | 3726 | try std.testing.expectEqual(FileExt.shared_library, classifyFileExt("foo.so")); |
| 3715 | 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 | 296 | \\ .c C source code (requires LLVM extensions) |
| 297 | 297 | \\ .cxx .cc .C .cpp C++ source code (requires LLVM extensions) |
| 298 | 298 | \\ .m Objective-C source code (requires LLVM extensions) |
| 299 | \\ .mm Objective-C++ source code (requires LLVM extensions) | |
| 299 | 300 | \\ .bc LLVM IR Module (requires LLVM extensions) |
| 300 | 301 | \\ |
| 301 | 302 | \\General Options: |
| ... | ... | @@ -1190,7 +1191,7 @@ fn buildOutputType( |
| 1190 | 1191 | .object, .static_library, .shared_library => { |
| 1191 | 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 | 1195 | try c_source_files.append(.{ |
| 1195 | 1196 | .src_path = arg, |
| 1196 | 1197 | .extra_flags = try arena.dupe([]const u8, extra_cflags.items), |
| ... | ... | @@ -1256,7 +1257,7 @@ fn buildOutputType( |
| 1256 | 1257 | .positional => { |
| 1257 | 1258 | const file_ext = Compilation.classifyFileExt(mem.spanZ(it.only_arg)); |
| 1258 | 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 | 1261 | .unknown, .shared_library, .object, .static_library => { |
| 1261 | 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 | 69 | .build_modes = true, |
| 70 | 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 | 78 | // Ensure the development tools are buildable. |
| 74 | 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 | } |