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(
33073307 try argv.appendSlice(&[_][]const u8{ "-target", llvm_triple });
33083308
33093309 switch (ext) {
3310 .c, .cpp, .m, .h => {
3310 .c, .cpp, .m, .mm, .h => {
33113311 try argv.appendSlice(&[_][]const u8{
33123312 "-nostdinc",
33133313 "-fno-spell-checking",
......@@ -3316,6 +3316,10 @@ pub fn addCCArgs(
33163316 try argv.append("-flto");
33173317 }
33183318
3319 if (ext == .mm) {
3320 try argv.append("-ObjC++");
3321 }
3322
33193323 // According to Rich Felker libc headers are supposed to go before C language headers.
33203324 // However as noted by @dimenus, appending libc headers before c_headers breaks intrinsics
33213325 // and other compiler specific items.
......@@ -3599,6 +3603,7 @@ pub const FileExt = enum {
35993603 cpp,
36003604 h,
36013605 m,
3606 mm,
36023607 ll,
36033608 bc,
36043609 assembly,
......@@ -3610,7 +3615,7 @@ pub const FileExt = enum {
36103615
36113616 pub fn clangSupportsDepFile(ext: FileExt) bool {
36123617 return switch (ext) {
3613 .c, .cpp, .h, .m => true,
3618 .c, .cpp, .h, .m, .mm => true,
36143619
36153620 .ll,
36163621 .bc,
......@@ -3648,6 +3653,10 @@ pub fn hasObjCExt(filename: []const u8) bool {
36483653 return mem.endsWith(u8, filename, ".m");
36493654}
36503655
3656pub fn hasObjCppExt(filename: []const u8) bool {
3657 return mem.endsWith(u8, filename, ".mm");
3658}
3659
36513660pub fn hasAsmExt(filename: []const u8) bool {
36523661 return mem.endsWith(u8, filename, ".s") or mem.endsWith(u8, filename, ".S");
36533662}
......@@ -3686,6 +3695,8 @@ pub fn classifyFileExt(filename: []const u8) FileExt {
36863695 return .cpp;
36873696 } else if (hasObjCExt(filename)) {
36883697 return .m;
3698 } else if (hasObjCppExt(filename)) {
3699 return .mm;
36893700 } else if (mem.endsWith(u8, filename, ".ll")) {
36903701 return .ll;
36913702 } else if (mem.endsWith(u8, filename, ".bc")) {
......@@ -3710,6 +3721,7 @@ pub fn classifyFileExt(filename: []const u8) FileExt {
37103721test "classifyFileExt" {
37113722 try std.testing.expectEqual(FileExt.cpp, classifyFileExt("foo.cc"));
37123723 try std.testing.expectEqual(FileExt.m, classifyFileExt("foo.m"));
3724 try std.testing.expectEqual(FileExt.mm, classifyFileExt("foo.mm"));
37133725 try std.testing.expectEqual(FileExt.unknown, classifyFileExt("foo.nim"));
37143726 try std.testing.expectEqual(FileExt.shared_library, classifyFileExt("foo.so"));
37153727 try std.testing.expectEqual(FileExt.shared_library, classifyFileExt("foo.so.1"));
src/main.zig+3-2
......@@ -296,6 +296,7 @@ const usage_build_generic =
296296 \\ .c C source code (requires LLVM extensions)
297297 \\ .cxx .cc .C .cpp C++ source code (requires LLVM extensions)
298298 \\ .m Objective-C source code (requires LLVM extensions)
299 \\ .mm Objective-C++ source code (requires LLVM extensions)
299300 \\ .bc LLVM IR Module (requires LLVM extensions)
300301 \\
301302 \\General Options:
......@@ -1190,7 +1191,7 @@ fn buildOutputType(
11901191 .object, .static_library, .shared_library => {
11911192 try link_objects.append(arg);
11921193 },
1193 .assembly, .c, .cpp, .h, .ll, .bc, .m => {
1194 .assembly, .c, .cpp, .h, .ll, .bc, .m, .mm => {
11941195 try c_source_files.append(.{
11951196 .src_path = arg,
11961197 .extra_flags = try arena.dupe([]const u8, extra_cflags.items),
......@@ -1256,7 +1257,7 @@ fn buildOutputType(
12561257 .positional => {
12571258 const file_ext = Compilation.classifyFileExt(mem.spanZ(it.only_arg));
12581259 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 }),
12601261 .unknown, .shared_library, .object, .static_library => {
12611262 try link_objects.append(it.only_arg);
12621263 },
test/standalone.zig+5
......@@ -69,6 +69,11 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
6969 .build_modes = true,
7070 .requires_macos_sdk = true,
7171 });
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
7378 // Ensure the development tools are buildable.
7479 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}