zig run/cc: recognize "-x language"

This commit adds support for "-x language" for a couple of hand-picked
supported languages. There is no reason the list of supported languages
to not grow (e.g. add "c-header"), but I'd like to keep it small at the
start.

Alternative 1
-------------

I first tried to add a new type "Language", and then add that to the
`CSourceFile`. But oh boy what a change it turns out to be. So I am
keeping myself tied to FileExt and see what you folks think.

Alternative 2
-------------

I tried adding `Language: ?[]const u8` to `CSourceFile`. However, the
language/ext, whatever we want to call it, still needs to be interpreted
in the main loop: one kind of handling for source files, other kind of
handling for everything else.

Test case
---------

*standalone.c*

    #include <iostream>

    int main() {
        std::cout << "elho\n";
    }

Compile and run:

    $ ./zig run -x c++ -lc++ standalone.c
    elho
    $ ./zig c++ -x c++ standalone.c -o standalone && ./standalone
    elho

Fixes #10915
This commit is contained in:
Motiejus Jakštys 2022-11-14 04:15:04 +02:00 committed by Andrew Kelley
parent d813cef42a
commit 6b3f59c3a7
5 changed files with 104 additions and 46 deletions

View File

@ -192,12 +192,30 @@ pub const CRTFile = struct {
} }
}; };
// supported languages for "zig clang -x <lang>".
// Loosely based on llvm-project/clang/include/clang/Driver/Types.def
pub const LangToExt = std.ComptimeStringMap(FileExt, .{
.{ "c", .c },
.{ "c-header", .h },
.{ "c++", .cpp },
.{ "c++-header", .h },
.{ "objective-c", .m },
.{ "objective-c-header", .h },
.{ "objective-c++", .mm },
.{ "objective-c++-header", .h },
.{ "assembler", .assembly },
.{ "assembler-with-cpp", .assembly_with_cpp },
.{ "cuda", .cu },
});
/// For passing to a C compiler. /// For passing to a C compiler.
pub const CSourceFile = struct { pub const CSourceFile = struct {
src_path: []const u8, src_path: []const u8,
extra_flags: []const []const u8 = &.{}, extra_flags: []const []const u8 = &.{},
/// Same as extra_flags except they are not added to the Cache hash. /// Same as extra_flags except they are not added to the Cache hash.
cache_exempt_flags: []const []const u8 = &.{}, cache_exempt_flags: []const []const u8 = &.{},
// this field is non-null iff language was explicitly set with "-x lang".
ext: ?FileExt = null,
}; };
const Job = union(enum) { const Job = union(enum) {
@ -2612,6 +2630,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
for (comp.c_object_table.keys()) |key| { for (comp.c_object_table.keys()) |key| {
_ = try man.addFile(key.src.src_path, null); _ = try man.addFile(key.src.src_path, null);
man.hash.addOptional(key.src.ext);
man.hash.addListOfBytes(key.src.extra_flags); man.hash.addListOfBytes(key.src.extra_flags);
} }
@ -3926,14 +3945,23 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P
break :e o_ext; break :e o_ext;
}; };
const o_basename = try std.fmt.allocPrint(arena, "{s}{s}", .{ o_basename_noext, out_ext }); const o_basename = try std.fmt.allocPrint(arena, "{s}{s}", .{ o_basename_noext, out_ext });
const ext = c_object.src.ext orelse classifyFileExt(c_object.src.src_path);
try argv.appendSlice(&[_][]const u8{ try argv.appendSlice(&[_][]const u8{ self_exe_path, "clang" });
self_exe_path, // if "ext" is explicit, add "-x <lang>". Otherwise let clang do its thing.
"clang", if (c_object.src.ext != null) {
c_object.src.src_path, try argv.appendSlice(&[_][]const u8{ "-x", switch (ext) {
}); .assembly => "assembler",
.assembly_with_cpp => "assembler-with-cpp",
const ext = classifyFileExt(c_object.src.src_path); .c => "c",
.cpp => "c++",
.cu => "cuda",
.m => "objective-c",
.mm => "objective-c++",
else => fatal("language '{s}' is unsupported in this context", .{@tagName(ext)}),
} });
}
try argv.append(c_object.src.src_path);
// When all these flags are true, it means that the entire purpose of // When all these flags are true, it means that the entire purpose of
// this compilation is to perform a single zig cc operation. This means // this compilation is to perform a single zig cc operation. This means
@ -4395,7 +4423,7 @@ pub fn addCCArgs(
} }
}, },
.shared_library, .ll, .bc, .unknown, .static_library, .object, .def, .zig => {}, .shared_library, .ll, .bc, .unknown, .static_library, .object, .def, .zig => {},
.assembly => { .assembly, .assembly_with_cpp => {
// The Clang assembler does not accept the list of CPU features like the // The Clang assembler does not accept the list of CPU features like the
// compiler frontend does. Therefore we must hard-code the -m flags for // compiler frontend does. Therefore we must hard-code the -m flags for
// all CPU features here. // all CPU features here.
@ -4535,6 +4563,7 @@ pub const FileExt = enum {
ll, ll,
bc, bc,
assembly, assembly,
assembly_with_cpp,
shared_library, shared_library,
object, object,
static_library, static_library,
@ -4549,6 +4578,7 @@ pub const FileExt = enum {
.ll, .ll,
.bc, .bc,
.assembly, .assembly,
.assembly_with_cpp,
.shared_library, .shared_library,
.object, .object,
.static_library, .static_library,
@ -4588,10 +4618,6 @@ pub fn hasObjCppExt(filename: []const u8) bool {
return mem.endsWith(u8, filename, ".mm"); 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");
}
pub fn hasSharedLibraryExt(filename: []const u8) bool { pub fn hasSharedLibraryExt(filename: []const u8) bool {
if (mem.endsWith(u8, filename, ".so") or if (mem.endsWith(u8, filename, ".so") or
mem.endsWith(u8, filename, ".dll") or mem.endsWith(u8, filename, ".dll") or
@ -4632,8 +4658,10 @@ pub fn classifyFileExt(filename: []const u8) FileExt {
return .ll; return .ll;
} else if (mem.endsWith(u8, filename, ".bc")) { } else if (mem.endsWith(u8, filename, ".bc")) {
return .bc; return .bc;
} else if (hasAsmExt(filename)) { } else if (mem.endsWith(u8, filename, ".s")) {
return .assembly; return .assembly;
} else if (mem.endsWith(u8, filename, ".S")) {
return .assembly_with_cpp;
} else if (mem.endsWith(u8, filename, ".h")) { } else if (mem.endsWith(u8, filename, ".h")) {
return .h; return .h;
} else if (mem.endsWith(u8, filename, ".zig")) { } else if (mem.endsWith(u8, filename, ".zig")) {

View File

@ -7171,6 +7171,13 @@ joinpd1("d"),
.psl = true, .psl = true,
}, },
jspd1("u"), jspd1("u"),
jspd1("x"), .{
.name = "x",
.syntax = .joined_or_separate,
.zig_equivalent = .x,
.pd1 = true,
.pd2 = false,
.psl = false,
},
joinpd1("y"), joinpd1("y"),
};}; };};

View File

@ -48,7 +48,7 @@ pub fn buildStaticLib(comp: *Compilation) !void {
try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libcxx", "include" }), try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libcxx", "include" }),
}); });
}, },
.assembly => {}, .assembly_with_cpp => {},
else => unreachable, // You can see the entire list of files just above. else => unreachable, // You can see the entire list of files just above.
} }
try cflags.append("-I"); try cflags.append("-I");

View File

@ -391,6 +391,7 @@ const usage_build_generic =
\\ -mcmodel=[default|tiny| Limit range of code and data virtual addresses \\ -mcmodel=[default|tiny| Limit range of code and data virtual addresses
\\ small|kernel| \\ small|kernel|
\\ medium|large] \\ medium|large]
\\ -x language Treat subsequent input files as having type <language>
\\ -mred-zone Force-enable the "red-zone" \\ -mred-zone Force-enable the "red-zone"
\\ -mno-red-zone Force-disable the "red-zone" \\ -mno-red-zone Force-disable the "red-zone"
\\ -fomit-frame-pointer Omit the stack frame pointer \\ -fomit-frame-pointer Omit the stack frame pointer
@ -913,6 +914,7 @@ fn buildOutputType(
var cssan = ClangSearchSanitizer.init(gpa, &clang_argv); var cssan = ClangSearchSanitizer.init(gpa, &clang_argv);
defer cssan.map.deinit(); defer cssan.map.deinit();
var file_ext: ?Compilation.FileExt = null;
args_loop: while (args_iter.next()) |arg| { args_loop: while (args_iter.next()) |arg| {
if (mem.startsWith(u8, arg, "@")) { if (mem.startsWith(u8, arg, "@")) {
// This is a "compiler response file". We must parse the file and treat its // This is a "compiler response file". We must parse the file and treat its
@ -1401,6 +1403,15 @@ fn buildOutputType(
try clang_argv.append(arg); try clang_argv.append(arg);
} else if (mem.startsWith(u8, arg, "-I")) { } else if (mem.startsWith(u8, arg, "-I")) {
try cssan.addIncludePath(.I, arg, arg[2..], true); try cssan.addIncludePath(.I, arg, arg[2..], true);
} else if (mem.eql(u8, arg, "-x")) {
const lang = args_iter.nextOrFatal();
if (mem.eql(u8, lang, "none")) {
file_ext = null;
} else if (Compilation.LangToExt.get(lang)) |got_ext| {
file_ext = got_ext;
} else {
fatal("language not recognized: '{s}'", .{lang});
}
} else if (mem.startsWith(u8, arg, "-mexec-model=")) { } else if (mem.startsWith(u8, arg, "-mexec-model=")) {
wasi_exec_model = std.meta.stringToEnum(std.builtin.WasiExecModel, arg["-mexec-model=".len..]) orelse { wasi_exec_model = std.meta.stringToEnum(std.builtin.WasiExecModel, arg["-mexec-model=".len..]) orelse {
fatal("expected [command|reactor] for -mexec-mode=[value], found '{s}'", .{arg["-mexec-model=".len..]}); fatal("expected [command|reactor] for -mexec-mode=[value], found '{s}'", .{arg["-mexec-model=".len..]});
@ -1408,22 +1419,21 @@ fn buildOutputType(
} else { } else {
fatal("unrecognized parameter: '{s}'", .{arg}); fatal("unrecognized parameter: '{s}'", .{arg});
} }
} else switch (Compilation.classifyFileExt(arg)) { } else switch (file_ext orelse
.object, .static_library, .shared_library => { Compilation.classifyFileExt(arg)) {
try link_objects.append(.{ .path = arg }); .object, .static_library, .shared_library => try link_objects.append(.{ .path = arg }),
}, .assembly, .assembly_with_cpp, .c, .cpp, .h, .ll, .bc, .m, .mm, .cu => {
.assembly, .c, .cpp, .h, .ll, .bc, .m, .mm, .cu => {
try c_source_files.append(.{ try c_source_files.append(.{
.src_path = arg, .src_path = arg,
.extra_flags = try arena.dupe([]const u8, extra_cflags.items), .extra_flags = try arena.dupe([]const u8, extra_cflags.items),
// duped when parsing the args.
.ext = file_ext,
}); });
}, },
.zig => { .zig => {
if (root_src_file) |other| { if (root_src_file) |other| {
fatal("found another zig file '{s}' after root source file '{s}'", .{ arg, other }); fatal("found another zig file '{s}' after root source file '{s}'", .{ arg, other });
} else { } else root_src_file = arg;
root_src_file = arg;
}
}, },
.def, .unknown => { .def, .unknown => {
fatal("unrecognized file extension of parameter '{s}'", .{arg}); fatal("unrecognized file extension of parameter '{s}'", .{arg});
@ -1464,6 +1474,7 @@ fn buildOutputType(
var needed = false; var needed = false;
var must_link = false; var must_link = false;
var force_static_libs = false; var force_static_libs = false;
var file_ext: ?Compilation.FileExt = null;
while (it.has_next) { while (it.has_next) {
it.next() catch |err| { it.next() catch |err| {
fatal("unable to parse command line parameters: {s}", .{@errorName(err)}); fatal("unable to parse command line parameters: {s}", .{@errorName(err)});
@ -1484,32 +1495,39 @@ fn buildOutputType(
.asm_only => c_out_mode = .assembly, // -S .asm_only => c_out_mode = .assembly, // -S
.preprocess_only => c_out_mode = .preprocessor, // -E .preprocess_only => c_out_mode = .preprocessor, // -E
.emit_llvm => emit_llvm = true, .emit_llvm => emit_llvm = true,
.x => {
const lang = mem.sliceTo(it.only_arg, 0);
if (mem.eql(u8, lang, "none")) {
file_ext = null;
} else if (Compilation.LangToExt.get(lang)) |got_ext| {
file_ext = got_ext;
} else {
fatal("language not recognized: '{s}'", .{lang});
}
},
.other => { .other => {
try clang_argv.appendSlice(it.other_args); try clang_argv.appendSlice(it.other_args);
}, },
.positional => { .positional => switch (file_ext orelse
const file_ext = Compilation.classifyFileExt(mem.sliceTo(it.only_arg, 0)); Compilation.classifyFileExt(mem.sliceTo(it.only_arg, 0))) {
switch (file_ext) { .assembly, .assembly_with_cpp, .c, .cpp, .ll, .bc, .h, .m, .mm, .cu => {
.assembly, .c, .cpp, .ll, .bc, .h, .m, .mm, .cu => { try c_source_files.append(.{
try c_source_files.append(.{ .src_path = it.only_arg }); .src_path = it.only_arg,
}, .ext = file_ext, // duped while parsing the args.
.unknown, .shared_library, .object, .static_library => { });
try link_objects.append(.{ },
.path = it.only_arg, .unknown, .shared_library, .object, .static_library => try link_objects.append(.{
.must_link = must_link, .path = it.only_arg,
}); .must_link = must_link,
}, }),
.def => { .def => {
linker_module_definition_file = it.only_arg; linker_module_definition_file = it.only_arg;
}, },
.zig => { .zig => {
if (root_src_file) |other| { if (root_src_file) |other| {
fatal("found another zig file '{s}' after root source file '{s}'", .{ it.only_arg, other }); fatal("found another zig file '{s}' after root source file '{s}'", .{ it.only_arg, other });
} else { } else root_src_file = it.only_arg;
root_src_file = it.only_arg; },
}
},
}
}, },
.l => { .l => {
// -l // -l
@ -4860,6 +4878,7 @@ pub const ClangArgIterator = struct {
o, o,
c, c,
m, m,
x,
other, other,
positional, positional,
l, l,

View File

@ -500,6 +500,10 @@ const known_options = [_]KnownOpt{
.name = "undefined", .name = "undefined",
.ident = "undefined", .ident = "undefined",
}, },
.{
.name = "x",
.ident = "x",
},
}; };
const blacklisted_options = [_][]const u8{}; const blacklisted_options = [_][]const u8{};