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>
This commit is contained in:
Stephen Gutekanst 2021-11-22 00:44:49 -07:00 committed by GitHub
parent 722c6b9567
commit 9836f1b2f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 90 additions and 4 deletions

View File

@ -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"));

View File

@ -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);
},

View File

@ -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");

View File

@ -0,0 +1,7 @@
#import <Foundation/Foundation.h>
@interface Foo : NSObject
- (NSString *)name;
@end

View File

@ -0,0 +1,11 @@
#import "Foo.h"
@implementation Foo
- (NSString *)name
{
NSString *str = [[NSString alloc] initWithFormat:@"Zig"];
return str;
}
@end

View File

@ -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);
}
}

View File

@ -0,0 +1,14 @@
#import "Foo.h"
#import <assert.h>
#include <iostream>
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;
}
}