Commit 6b3f59c3a7

Motiejus Jakštys <motiejus@jakstys.lt>
2022-11-14 03:15:04
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
1 parent d813cef
src/clang_options_data.zig
@@ -7171,6 +7171,13 @@ joinpd1("d"),
     .psl = true,
 },
 jspd1("u"),
-jspd1("x"),
+.{
+    .name = "x",
+    .syntax = .joined_or_separate,
+    .zig_equivalent = .x,
+    .pd1 = true,
+    .pd2 = false,
+    .psl = false,
+},
 joinpd1("y"),
 };};
src/Compilation.zig
@@ -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.
 pub const CSourceFile = struct {
     src_path: []const u8,
     extra_flags: []const []const u8 = &.{},
     /// Same as extra_flags except they are not added to the Cache hash.
     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) {
@@ -2612,6 +2630,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
 
     for (comp.c_object_table.keys()) |key| {
         _ = try man.addFile(key.src.src_path, null);
+        man.hash.addOptional(key.src.ext);
         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;
         };
         const o_basename = try std.fmt.allocPrint(arena, "{s}{s}", .{ o_basename_noext, out_ext });
-
-        try argv.appendSlice(&[_][]const u8{
-            self_exe_path,
-            "clang",
-            c_object.src.src_path,
-        });
-
-        const ext = classifyFileExt(c_object.src.src_path);
+        const ext = c_object.src.ext orelse classifyFileExt(c_object.src.src_path);
+
+        try argv.appendSlice(&[_][]const u8{ self_exe_path, "clang" });
+        // if "ext" is explicit, add "-x <lang>". Otherwise let clang do its thing.
+        if (c_object.src.ext != null) {
+            try argv.appendSlice(&[_][]const u8{ "-x", switch (ext) {
+                .assembly => "assembler",
+                .assembly_with_cpp => "assembler-with-cpp",
+                .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
         // 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 => {},
-        .assembly => {
+        .assembly, .assembly_with_cpp => {
             // 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
             // all CPU features here.
@@ -4535,6 +4563,7 @@ pub const FileExt = enum {
     ll,
     bc,
     assembly,
+    assembly_with_cpp,
     shared_library,
     object,
     static_library,
@@ -4549,6 +4578,7 @@ pub const FileExt = enum {
             .ll,
             .bc,
             .assembly,
+            .assembly_with_cpp,
             .shared_library,
             .object,
             .static_library,
@@ -4588,10 +4618,6 @@ 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");
-}
-
 pub fn hasSharedLibraryExt(filename: []const u8) bool {
     if (mem.endsWith(u8, filename, ".so") or
         mem.endsWith(u8, filename, ".dll") or
@@ -4632,8 +4658,10 @@ pub fn classifyFileExt(filename: []const u8) FileExt {
         return .ll;
     } else if (mem.endsWith(u8, filename, ".bc")) {
         return .bc;
-    } else if (hasAsmExt(filename)) {
+    } else if (mem.endsWith(u8, filename, ".s")) {
         return .assembly;
+    } else if (mem.endsWith(u8, filename, ".S")) {
+        return .assembly_with_cpp;
     } else if (mem.endsWith(u8, filename, ".h")) {
         return .h;
     } else if (mem.endsWith(u8, filename, ".zig")) {
src/libunwind.zig
@@ -48,7 +48,7 @@ pub fn buildStaticLib(comp: *Compilation) !void {
                     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.
         }
         try cflags.append("-I");
src/main.zig
@@ -391,6 +391,7 @@ const usage_build_generic =
     \\  -mcmodel=[default|tiny|   Limit range of code and data virtual addresses
     \\            small|kernel|
     \\            medium|large]
+    \\  -x language               Treat subsequent input files as having type <language>
     \\  -mred-zone                Force-enable the "red-zone"
     \\  -mno-red-zone             Force-disable the "red-zone"
     \\  -fomit-frame-pointer      Omit the stack frame pointer
@@ -913,6 +914,7 @@ fn buildOutputType(
             var cssan = ClangSearchSanitizer.init(gpa, &clang_argv);
             defer cssan.map.deinit();
 
+            var file_ext: ?Compilation.FileExt = null;
             args_loop: while (args_iter.next()) |arg| {
                 if (mem.startsWith(u8, arg, "@")) {
                     // 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);
                     } else if (mem.startsWith(u8, arg, "-I")) {
                         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=")) {
                         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..]});
@@ -1408,22 +1419,21 @@ fn buildOutputType(
                     } else {
                         fatal("unrecognized parameter: '{s}'", .{arg});
                     }
-                } else switch (Compilation.classifyFileExt(arg)) {
-                    .object, .static_library, .shared_library => {
-                        try link_objects.append(.{ .path = arg });
-                    },
-                    .assembly, .c, .cpp, .h, .ll, .bc, .m, .mm, .cu => {
+                } else switch (file_ext orelse
+                    Compilation.classifyFileExt(arg)) {
+                    .object, .static_library, .shared_library => try link_objects.append(.{ .path = arg }),
+                    .assembly, .assembly_with_cpp, .c, .cpp, .h, .ll, .bc, .m, .mm, .cu => {
                         try c_source_files.append(.{
                             .src_path = arg,
                             .extra_flags = try arena.dupe([]const u8, extra_cflags.items),
+                            // duped when parsing the args.
+                            .ext = file_ext,
                         });
                     },
                     .zig => {
                         if (root_src_file) |other| {
                             fatal("found another zig file '{s}' after root source file '{s}'", .{ arg, other });
-                        } else {
-                            root_src_file = arg;
-                        }
+                        } else root_src_file = arg;
                     },
                     .def, .unknown => {
                         fatal("unrecognized file extension of parameter '{s}'", .{arg});
@@ -1464,6 +1474,7 @@ fn buildOutputType(
             var needed = false;
             var must_link = false;
             var force_static_libs = false;
+            var file_ext: ?Compilation.FileExt = null;
             while (it.has_next) {
                 it.next() catch |err| {
                     fatal("unable to parse command line parameters: {s}", .{@errorName(err)});
@@ -1484,32 +1495,39 @@ fn buildOutputType(
                     .asm_only => c_out_mode = .assembly, // -S
                     .preprocess_only => c_out_mode = .preprocessor, // -E
                     .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 => {
                         try clang_argv.appendSlice(it.other_args);
                     },
-                    .positional => {
-                        const file_ext = Compilation.classifyFileExt(mem.sliceTo(it.only_arg, 0));
-                        switch (file_ext) {
-                            .assembly, .c, .cpp, .ll, .bc, .h, .m, .mm, .cu => {
-                                try c_source_files.append(.{ .src_path = it.only_arg });
-                            },
-                            .unknown, .shared_library, .object, .static_library => {
-                                try link_objects.append(.{
-                                    .path = it.only_arg,
-                                    .must_link = must_link,
-                                });
-                            },
-                            .def => {
-                                linker_module_definition_file = it.only_arg;
-                            },
-                            .zig => {
-                                if (root_src_file) |other| {
-                                    fatal("found another zig file '{s}' after root source file '{s}'", .{ it.only_arg, other });
-                                } else {
-                                    root_src_file = it.only_arg;
-                                }
-                            },
-                        }
+                    .positional => switch (file_ext orelse
+                        Compilation.classifyFileExt(mem.sliceTo(it.only_arg, 0))) {
+                        .assembly, .assembly_with_cpp, .c, .cpp, .ll, .bc, .h, .m, .mm, .cu => {
+                            try c_source_files.append(.{
+                                .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,
+                            .must_link = must_link,
+                        }),
+                        .def => {
+                            linker_module_definition_file = it.only_arg;
+                        },
+                        .zig => {
+                            if (root_src_file) |other| {
+                                fatal("found another zig file '{s}' after root source file '{s}'", .{ it.only_arg, other });
+                            } else root_src_file = it.only_arg;
+                        },
                     },
                     .l => {
                         // -l
@@ -4860,6 +4878,7 @@ pub const ClangArgIterator = struct {
         o,
         c,
         m,
+        x,
         other,
         positional,
         l,
tools/update_clang_options.zig
@@ -500,6 +500,10 @@ const known_options = [_]KnownOpt{
         .name = "undefined",
         .ident = "undefined",
     },
+    .{
+        .name = "x",
+        .ident = "x",
+    },
 };
 
 const blacklisted_options = [_][]const u8{};