diff options
Diffstat (limited to 'src-self-hosted')
| -rw-r--r-- | src-self-hosted/clang_options.zig | 4 | ||||
| -rw-r--r-- | src-self-hosted/main.zig | 942 | ||||
| -rw-r--r-- | src-self-hosted/stage2.zig | 280 |
3 files changed, 875 insertions, 351 deletions
diff --git a/src-self-hosted/clang_options.zig b/src-self-hosted/clang_options.zig index 1b70c71dac..42bfecb746 100644 --- a/src-self-hosted/clang_options.zig +++ b/src-self-hosted/clang_options.zig @@ -7,9 +7,7 @@ pub const CliArg = struct { name: []const u8, syntax: Syntax, - /// TODO we're going to want to change this when we start shipping self-hosted because this causes - /// all the functions in stage2.zig to get exported. - zig_equivalent: @import("stage2.zig").ClangArgIterator.ZigEquivalent, + zig_equivalent: @import("main.zig").ClangArgIterator.ZigEquivalent, /// Prefixed by "-" pd1: bool = false, diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index fb20a09f1d..330c0468f1 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const assert = std.debug.assert; const io = std.io; const fs = std.fs; const mem = std.mem; @@ -11,6 +12,13 @@ const link = @import("link.zig"); const Package = @import("Package.zig"); const zir = @import("zir.zig"); const build_options = @import("build_options"); +const warn = std.log.warn; +const info = std.log.info; + +fn fatal(comptime format: []const u8, args: anytype) noreturn { + std.log.emerg(format, args); + process.exit(1); +} pub const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB @@ -28,9 +36,11 @@ const usage = \\ build-exe [source] Create executable from source or object files \\ build-lib [source] Create library from source or object files \\ build-obj [source] Create object from source or assembly + \\ cc Use Zig as a drop-in C compiler + \\ c++ Use Zig as a drop-in C++ compiler + \\ env Print lib path, std path, compiler id and version \\ fmt [source] Parse file and render in canonical zig format \\ targets List available compilation targets - \\ env Print lib path, std path, compiler id and version \\ version Print version number and exit \\ zen Print zen of zig and exit \\ @@ -84,11 +94,19 @@ pub fn main() !void { const cmd = args[1]; const cmd_args = args[2..]; if (mem.eql(u8, cmd, "build-exe")) { - return buildOutputType(gpa, arena, cmd_args, .Exe); + return buildOutputType(gpa, arena, args, .{ .build = .Exe }); } else if (mem.eql(u8, cmd, "build-lib")) { - return buildOutputType(gpa, arena, cmd_args, .Lib); + return buildOutputType(gpa, arena, args, .{ .build = .Lib }); } else if (mem.eql(u8, cmd, "build-obj")) { - return buildOutputType(gpa, arena, cmd_args, .Obj); + return buildOutputType(gpa, arena, args, .{ .build = .Obj }); + } else if (mem.eql(u8, cmd, "cc")) { + return buildOutputType(gpa, arena, args, .cc); + } else if (mem.eql(u8, cmd, "c++")) { + return buildOutputType(gpa, arena, args, .cpp); + } else if (mem.eql(u8, cmd, "clang") or + mem.eql(u8, cmd, "-cc1") or mem.eql(u8, cmd, "-cc1as")) + { + return punt_to_clang(arena, args); } else if (mem.eql(u8, cmd, "fmt")) { return cmdFmt(gpa, cmd_args); } else if (mem.eql(u8, cmd, "targets")) { @@ -147,6 +165,8 @@ const usage_build_generic = \\ ReleaseFast Optimizations on, safety off \\ ReleaseSafe Optimizations on, safety on \\ ReleaseSmall Optimize for small binary, safety off + \\ -fPIC Force-enable Position Independent Code + \\ -fno-PIC Force-disable Position Independent Code \\ --dynamic Force output to be dynamically linked \\ --strip Exclude debug symbols \\ -ofmt=[mode] Override target object format @@ -158,11 +178,19 @@ const usage_build_generic = \\ macho (planned) macOS relocatables \\ hex (planned) Intel IHEX \\ raw (planned) Dump machine code directly + \\ -dirafter [dir] Add directory to AFTER include search path + \\ -isystem [dir] Add directory to SYSTEM include search path + \\ -I[dir] Add directory to include search path + \\ -D[macro]=[value] Define C [macro] to [value] (1 if [value] omitted) \\ \\Link Options: \\ -l[lib], --library [lib] Link against system library + \\ -L[d], --library-directory [d] Add a directory to the library search path + \\ -T[script] Use a custom linker script \\ --dynamic-linker [path] Set the dynamic interpreter path (usually ld.so) \\ --version [ver] Dynamic library semver + \\ -rdynamic Add all symbols to the dynamic symbol table + \\ -rpath [path] Add directory to the runtime library search path \\ \\Debug Options (Zig Compiler Development): \\ -ftime-report Print timing diagnostics @@ -181,11 +209,15 @@ const Emit = union(enum) { yes: []const u8, }; -fn buildOutputType( +pub fn buildOutputType( gpa: *Allocator, arena: *Allocator, - args: []const []const u8, - output_mode: std.builtin.OutputMode, + all_args: []const []const u8, + arg_mode: union(enum) { + build: std.builtin.OutputMode, + cc, + cpp, + }, ) !void { var color: Color = .Auto; var build_mode: std.builtin.Mode = .Debug; @@ -194,6 +226,7 @@ fn buildOutputType( var root_src_file: ?[]const u8 = null; var version: std.builtin.Version = .{ .major = 0, .minor = 0, .patch = 0 }; var strip = false; + var emit_h = true; var watch = false; var debug_tokenize = false; var debug_ast_tree = false; @@ -201,6 +234,7 @@ fn buildOutputType( var debug_link = false; var debug_ir = false; var debug_codegen = false; + var debug_cc = false; var time_report = false; var emit_bin: Emit = .yes_default_path; var emit_zir: Emit = .no; @@ -208,11 +242,57 @@ fn buildOutputType( var target_mcpu: ?[]const u8 = null; var target_dynamic_linker: ?[]const u8 = null; var target_ofmt: ?[]const u8 = null; + var output_mode: std.builtin.OutputMode = undefined; + var ensure_libc_on_non_freestanding = false; + var ensure_libcpp_on_non_freestanding = false; + var have_libc = false; + var have_libcpp = false; + var want_native_include_dirs = false; + var enable_cache: ?bool = null; + var want_pic: ?bool = null; + var want_sanitize_c: ?bool = null; + var rdynamic: bool = false; + var only_pp_or_asm = false; + var linker_script: ?[]const u8 = null; + var version_script: ?[]const u8 = null; + var disable_c_depfile = false; + var override_soname: ?[]const u8 = null; + var linker_optimization: ?[]const u8 = null; + var linker_gc_sections: ?bool = null; + var linker_allow_shlib_undefined: ?bool = null; + var linker_bind_global_refs_locally: ?bool = null; + var linker_z_nodelete = false; + var linker_z_defs = false; + var stack_size_override: u64 = 0; var system_libs = std.ArrayList([]const u8).init(gpa); defer system_libs.deinit(); - { + var clang_argv = std.ArrayList([]const u8).init(gpa); + defer clang_argv.deinit(); + + var lib_dirs = std.ArrayList([]const u8).init(gpa); + defer lib_dirs.deinit(); + + var rpath_list = std.ArrayList([]const u8).init(gpa); + defer rpath_list.deinit(); + + var c_source_files = std.ArrayList([]const u8).init(gpa); + defer c_source_files.deinit(); + + var link_objects = std.ArrayList([]const u8).init(gpa); + defer link_objects.deinit(); + + var framework_dirs = std.ArrayList([]const u8).init(gpa); + defer framework_dirs.deinit(); + + var frameworks = std.ArrayList([]const u8).init(gpa); + defer frameworks.deinit(); + + if (arg_mode == .build) { + output_mode = arg_mode.build; + + const args = all_args[2..]; var i: usize = 0; while (i < args.len) : (i += 1) { const arg = args[i]; @@ -222,8 +302,7 @@ fn buildOutputType( process.exit(0); } else if (mem.eql(u8, arg, "--color")) { if (i + 1 >= args.len) { - std.debug.print("expected [auto|on|off] after --color\n", .{}); - process.exit(1); + fatal("expected [auto|on|off] after --color", .{}); } i += 1; const next_arg = args[i]; @@ -234,13 +313,11 @@ fn buildOutputType( } else if (mem.eql(u8, next_arg, "off")) { color = .Off; } else { - std.debug.print("expected [auto|on|off] after --color, found '{}'\n", .{next_arg}); - process.exit(1); + fatal("expected [auto|on|off] after --color, found '{}'", .{next_arg}); } } else if (mem.eql(u8, arg, "--mode")) { if (i + 1 >= args.len) { - std.debug.print("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode\n", .{}); - process.exit(1); + fatal("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode", .{}); } i += 1; const next_arg = args[i]; @@ -253,44 +330,66 @@ fn buildOutputType( } else if (mem.eql(u8, next_arg, "ReleaseSmall")) { build_mode = .ReleaseSmall; } else { - std.debug.print("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode, found '{}'\n", .{next_arg}); - process.exit(1); + fatal("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode, found '{}'", .{next_arg}); } + } else if (mem.eql(u8, arg, "--stack")) { + if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + i += 1; + stack_size_override = std.fmt.parseInt(u64, args[i], 10) catch |err| { + fatal("unable to parse '{}': {}", .{ arg, @errorName(err) }); + }; } else if (mem.eql(u8, arg, "--name")) { - if (i + 1 >= args.len) { - std.debug.print("expected parameter after --name\n", .{}); - process.exit(1); - } + if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); i += 1; provided_name = args[i]; - } else if (mem.eql(u8, arg, "--library")) { - if (i + 1 >= args.len) { - std.debug.print("expected parameter after --library\n", .{}); - process.exit(1); - } + } else if (mem.eql(u8, arg, "-rpath")) { + if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + i += 1; + try rpath_list.append(args[i]); + } else if (mem.eql(u8, arg, "--library-directory") or mem.eql(u8, arg, "-L")) { + if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + i += 1; + try lib_dirs.append(args[i]); + } else if (mem.eql(u8, arg, "-T")) { + if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + i += 1; + linker_script = args[i]; + } else if (mem.eql(u8, arg, "--version-script")) { + if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + i += 1; + version_script = args[i]; + } else if (mem.eql(u8, arg, "--library") or mem.eql(u8, arg, "-l")) { + if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + // We don't know whether this library is part of libc or libc++ until we resolve the target. + // So we simply append to the list for now. i += 1; try system_libs.append(args[i]); + } else if (mem.eql(u8, arg, "-D") or + mem.eql(u8, arg, "-isystem") or + mem.eql(u8, arg, "-I") or + mem.eql(u8, arg, "-dirafter")) + { + if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + i += 1; + try clang_argv.append(arg); + try clang_argv.append(args[i]); } else if (mem.eql(u8, arg, "--version")) { if (i + 1 >= args.len) { - std.debug.print("expected parameter after --version\n", .{}); - process.exit(1); + fatal("expected parameter after --version", .{}); } i += 1; version = std.builtin.Version.parse(args[i]) catch |err| { - std.debug.print("unable to parse --version '{}': {}\n", .{ args[i], @errorName(err) }); - process.exit(1); + fatal("unable to parse --version '{}': {}", .{ args[i], @errorName(err) }); }; } else if (mem.eql(u8, arg, "-target")) { if (i + 1 >= args.len) { - std.debug.print("expected parameter after -target\n", .{}); - process.exit(1); + fatal("expected parameter after -target", .{}); } i += 1; target_arch_os_abi = args[i]; } else if (mem.eql(u8, arg, "-mcpu")) { if (i + 1 >= args.len) { - std.debug.print("expected parameter after -mcpu\n", .{}); - process.exit(1); + fatal("expected parameter after -mcpu", .{}); } i += 1; target_mcpu = args[i]; @@ -300,8 +399,7 @@ fn buildOutputType( target_mcpu = arg["-mcpu=".len..]; } else if (mem.eql(u8, arg, "--dynamic-linker")) { if (i + 1 >= args.len) { - std.debug.print("expected parameter after --dynamic-linker\n", .{}); - process.exit(1); + fatal("expected parameter after --dynamic-linker", .{}); } i += 1; target_dynamic_linker = args[i]; @@ -309,6 +407,12 @@ fn buildOutputType( watch = true; } else if (mem.eql(u8, arg, "-ftime-report")) { time_report = true; + } else if (mem.eql(u8, arg, "-fPIC")) { + want_pic = true; + } else if (mem.eql(u8, arg, "-fno-PIC")) { + want_pic = false; + } else if (mem.eql(u8, arg, "-rdynamic")) { + rdynamic = true; } else if (mem.eql(u8, arg, "-femit-bin")) { emit_bin = .yes_default_path; } else if (mem.startsWith(u8, arg, "-femit-bin=")) { @@ -327,6 +431,8 @@ fn buildOutputType( link_mode = .Static; } else if (mem.eql(u8, arg, "--strip")) { strip = true; + } else if (mem.eql(u8, arg, "-Bsymbolic")) { + linker_bind_global_refs_locally = true; } else if (mem.eql(u8, arg, "--debug-tokenize")) { debug_tokenize = true; } else if (mem.eql(u8, arg, "--debug-ast-tree")) { @@ -339,44 +445,321 @@ fn buildOutputType( debug_ir = true; } else if (mem.eql(u8, arg, "--debug-codegen")) { debug_codegen = true; + } else if (mem.eql(u8, arg, "--debug-cc")) { + debug_cc = true; + } else if (mem.startsWith(u8, arg, "-T")) { + linker_script = arg[2..]; + } else if (mem.startsWith(u8, arg, "-L")) { + try lib_dirs.append(arg[2..]); } else if (mem.startsWith(u8, arg, "-l")) { + // We don't know whether this library is part of libc or libc++ until we resolve the target. + // So we simply append to the list for now. try system_libs.append(arg[2..]); + } else if (mem.startsWith(u8, arg, "-D") or + mem.startsWith(u8, arg, "-I")) + { + try clang_argv.append(arg); } else { - std.debug.print("unrecognized parameter: '{}'\n", .{arg}); - process.exit(1); + fatal("unrecognized parameter: '{}'", .{arg}); } - } else if (mem.endsWith(u8, arg, ".s") or mem.endsWith(u8, arg, ".S")) { - std.debug.print("assembly files not supported yet\n", .{}); - process.exit(1); } else if (mem.endsWith(u8, arg, ".o") or mem.endsWith(u8, arg, ".obj") or mem.endsWith(u8, arg, ".a") or mem.endsWith(u8, arg, ".lib")) { - std.debug.print("object files and static libraries not supported yet\n", .{}); - process.exit(1); - } else if (mem.endsWith(u8, arg, ".c") or - mem.endsWith(u8, arg, ".cpp")) - { - std.debug.print("compilation of C and C++ source code requires LLVM extensions which are not implemented yet\n", .{}); - process.exit(1); + try link_objects.append(arg); + } else if (hasAsmExt(arg) or hasCExt(arg) or hasCppExt(arg)) { + try c_source_files.append(arg); } else if (mem.endsWith(u8, arg, ".so") or mem.endsWith(u8, arg, ".dylib") or mem.endsWith(u8, arg, ".dll")) { - std.debug.print("linking against dynamic libraries not yet supported\n", .{}); - process.exit(1); + fatal("linking against dynamic libraries not yet supported", .{}); } else if (mem.endsWith(u8, arg, ".zig") or mem.endsWith(u8, arg, ".zir")) { if (root_src_file) |other| { - std.debug.print("found another zig file '{}' after root source file '{}'\n", .{ arg, other }); - process.exit(1); + fatal("found another zig file '{}' after root source file '{}'", .{ arg, other }); } else { root_src_file = arg; } } else { - std.debug.print("unrecognized file extension of parameter '{}'\n", .{arg}); + fatal("unrecognized file extension of parameter '{}'", .{arg}); + } + } + } else { + if (!build_options.have_llvm) + fatal("`zig cc` and `zig c++` unavailable: compiler not built with LLVM extensions enabled", .{}); + emit_h = false; + strip = true; + ensure_libc_on_non_freestanding = true; + ensure_libcpp_on_non_freestanding = arg_mode == .cpp; + want_native_include_dirs = true; + + var c_arg = false; + var is_shared_lib = false; + var linker_args = std.ArrayList([]const u8).init(arena); + var it = ClangArgIterator.init(arena, all_args); + while (it.has_next) { + it.next() catch |err| { + fatal("unable to parse command line parameters: {}", .{@errorName(err)}); + }; + switch (it.zig_equivalent) { + .target => target_arch_os_abi = it.only_arg, // example: -target riscv64-linux-unknown + .o => { + // -o + emit_bin = .{ .yes = it.only_arg }; + enable_cache = true; + }, + .c => c_arg = true, // -c + .other => { + try clang_argv.appendSlice(it.other_args); + }, + .positional => { + const file_ext = classify_file_ext(mem.spanZ(it.only_arg)); + switch (file_ext) { + .assembly, .c, .cpp, .ll, .bc, .h => try c_source_files.append(it.only_arg), + .unknown => try link_objects.append(it.only_arg), + } + }, + .l => { + // -l + // We don't know whether this library is part of libc or libc++ until we resolve the target. + // So we simply append to the list for now. + try system_libs.append(it.only_arg); + }, + .ignore => {}, + .driver_punt => { + // Never mind what we're doing, just pass the args directly. For example --help. + return punt_to_clang(arena, all_args); + }, + .pic => want_pic = true, + .no_pic => want_pic = false, + .nostdlib => ensure_libc_on_non_freestanding = false, + .nostdlib_cpp => ensure_libcpp_on_non_freestanding = false, + .shared => { + link_mode = .Dynamic; + is_shared_lib = true; + }, + .rdynamic => rdynamic = true, + .wl => { + var split_it = mem.split(it.only_arg, ","); + @breakpoint(); // TODO the first arg is empty string right? skip past that. + while (split_it.next()) |linker_arg| { + try linker_args.append(linker_arg); + } + }, + .pp_or_asm => { + // This handles both -E and -S. + only_pp_or_asm = true; + try clang_argv.appendSlice(it.other_args); + }, + .optimize => { + // Alright, what release mode do they want? + if (mem.eql(u8, it.only_arg, "Os")) { + build_mode = .ReleaseSmall; + } else if (mem.eql(u8, it.only_arg, "O2") or + mem.eql(u8, it.only_arg, "O3") or + mem.eql(u8, it.only_arg, "O4")) + { + build_mode = .ReleaseFast; + } else if (mem.eql(u8, it.only_arg, "Og") or + mem.eql(u8, it.only_arg, "O0")) + { + build_mode = .Debug; + } else { + try clang_argv.appendSlice(it.other_args); + } + }, + .debug => { + strip = false; + if (mem.eql(u8, it.only_arg, "-g")) { + // We handled with strip = false above. + } else { + try clang_argv.appendSlice(it.other_args); + } + }, + .sanitize => { + if (mem.eql(u8, it.only_arg, "undefined")) { + want_sanitize_c = true; + } else { + try clang_argv.appendSlice(it.other_args); + } + }, + .linker_script => linker_script = it.only_arg, + .verbose_cmds => { + debug_cc = true; + debug_link = true; + }, + .for_linker => try linker_args.append(it.only_arg), + .linker_input_z => { + try linker_args.append("-z"); + try linker_args.append(it.only_arg); + }, + .lib_dir => try lib_dirs.append(it.only_arg), + .mcpu => target_mcpu = it.only_arg, + .dep_file => { + disable_c_depfile = true; + try clang_argv.appendSlice(it.other_args); + }, + .framework_dir => try framework_dirs.append(it.only_arg), + .framework => try frameworks.append(it.only_arg), + .nostdlibinc => want_native_include_dirs = false, + } + } + // Parse linker args. + var i: usize = 0; + while (i < linker_args.items.len) : (i += 1) { + const arg = linker_args.items[i]; + if (mem.eql(u8, arg, "-soname")) { + i += 1; + if (i >= linker_args.items.len) { + fatal("expected linker arg after '{}'", .{arg}); + } + const soname = linker_args.items[i]; + override_soname = soname; + // Use it as --name. + // Example: libsoundio.so.2 + var prefix: usize = 0; + if (mem.startsWith(u8, soname, "lib")) { + prefix = 3; + } + var end: usize = soname.len; + if (mem.endsWith(u8, soname, ".so")) { + end -= 3; + } else { + var found_digit = false; + while (end > 0 and std.ascii.isDigit(soname[end - 1])) { + found_digit = true; + end -= 1; + } + if (found_digit and end > 0 and soname[end - 1] == '.') { + end -= 1; + } else { + end = soname.len; + } + if (mem.endsWith(u8, soname[prefix..end], ".so")) { + end -= 3; + } + } + provided_name = soname[prefix..end]; + } else if (mem.eql(u8, arg, "-rpath")) { + i += 1; + if (i >= linker_args.items.len) { + fatal("expected linker arg after '{}'", .{arg}); + } + try rpath_list.append(linker_args.items[i]); + } else if (mem.eql(u8, arg, "-I") or + mem.eql(u8, arg, "--dynamic-linker") or + mem.eql(u8, arg, "-dynamic-linker")) + { + i += 1; + if (i >= linker_args.items.len) { + fatal("expected linker arg after '{}'", .{arg}); + } + target_dynamic_linker = linker_args.items[i]; + } else if (mem.eql(u8, arg, "-E") or + mem.eql(u8, arg, "--export-dynamic") or + mem.eql(u8, arg, "-export-dynamic")) + { + rdynamic = true; + } else if (mem.eql(u8, arg, "--version-script")) { + i += 1; + if (i >= linker_args.items.len) { + fatal("expected linker arg after '{}'", .{arg}); + } + version_script = linker_args.items[i]; + } else if (mem.startsWith(u8, arg, "-O")) { + linker_optimization = arg; + } else if (mem.eql(u8, arg, "--gc-sections")) { + linker_gc_sections = true; + } else if (mem.eql(u8, arg, "--no-gc-sections")) { + linker_gc_sections = false; + } else if (mem.eql(u8, arg, "--allow-shlib-undefined") or + mem.eql(u8, arg, "-allow-shlib-undefined")) + { + linker_allow_shlib_undefined = true; + } else if (mem.eql(u8, arg, "--no-allow-shlib-undefined") or + mem.eql(u8, arg, "-no-allow-shlib-undefined")) + { + linker_allow_shlib_undefined = false; + } else if (mem.eql(u8, arg, "-Bsymbolic")) { + linker_bind_global_refs_locally = true; + } else if (mem.eql(u8, arg, "-z")) { + i += 1; + if (i >= linker_args.items.len) { + fatal("expected linker arg after '{}'", .{arg}); + } + const z_arg = linker_args.items[i]; + if (mem.eql(u8, z_arg, "nodelete")) { + linker_z_nodelete = true; + } else if (mem.eql(u8, z_arg, "defs")) { + linker_z_defs = true; + } else { + warn("unsupported linker arg: -z {}", .{z_arg}); + } + } else if (mem.eql(u8, arg, "--major-image-version")) { + i += 1; + if (i >= linker_args.items.len) { + fatal("expected linker arg after '{}'", .{arg}); + } + version.major = std.fmt.parseInt(u32, linker_args.items[i], 10) catch |err| { + fatal("unable to parse '{}': {}", .{ arg, @errorName(err) }); + }; + } else if (mem.eql(u8, arg, "--minor-image-version")) { + i += 1; + if (i >= linker_args.items.len) { + fatal("expected linker arg after '{}'", .{arg}); + } + version.minor = std.fmt.parseInt(u32, linker_args.items[i], 10) catch |err| { + fatal("unable to parse '{}': {}", .{ arg, @errorName(err) }); + }; + } else if (mem.eql(u8, arg, "--stack")) { + i += 1; + if (i >= linker_args.items.len) { + fatal("expected linker arg after '{}'", .{arg}); + } + stack_size_override = std.fmt.parseInt(u64, linker_args.items[i], 10) catch |err| { + fatal("unable to parse '{}': {}", .{ arg, @errorName(err) }); + }; + } else { + warn("unsupported linker arg: {}", .{arg}); } } + + if (want_sanitize_c == true and build_mode == .ReleaseFast) { + build_mode = .ReleaseSafe; + } + + if (only_pp_or_asm) { + output_mode = .Obj; + fatal("TODO implement using zig cc as a preprocessor", .{}); + //// Transfer "link_objects" into c_source_files so that all those + //// args make it onto the command line. + //try c_source_files.appendSlice(link_objects.items); + //for (c_source_files.items) |c_source_file| { + // const src_path = switch (emit_bin) { + // .yes => |p| p, + // else => c_source_file.source_path, + // }; + // const basename = std.fs.path.basename(src_path); + // c_source_file.preprocessor_only_basename = basename; + //} + //emit_bin = .no; + } else if (!c_arg) { + output_mode = if (is_shared_lib) .Lib else .Exe; + switch (emit_bin) { + .no, .yes_default_path => { + emit_bin = .{ .yes = "a.out" }; + enable_cache = true; + }, + .yes => {}, + } + } else { + output_mode = .Obj; + } + if (c_source_files.items.len == 0 and link_objects.items.len == 0) { + // For example `zig cc` and no args should print the "no input files" message. + return punt_to_clang(arena, all_args); + } } const root_name = if (provided_name) |n| n else blk: { @@ -385,16 +768,10 @@ fn buildOutputType( var it = mem.split(basename, "."); break :blk it.next() orelse basename; } else { - std.debug.print("--name [name] not provided and unable to infer\n", .{}); - process.exit(1); + fatal("--name [name] not provided and unable to infer", .{}); } }; - if (system_libs.items.len != 0) { - std.debug.print("linking against system libraries not yet supported\n", .{}); - process.exit(1); - } - var diags: std.zig.CrossTarget.ParseOptions.Diagnostics = .{}; const cross_target = std.zig.CrossTarget.parse(.{ .arch_os_abi = target_arch_os_abi, @@ -429,17 +806,67 @@ fn buildOutputType( else => |e| return e, }; - var target_info = try std.zig.system.NativeTargetInfo.detect(gpa, cross_target); + const target_info = try std.zig.system.NativeTargetInfo.detect(gpa, cross_target); if (target_info.cpu_detection_unimplemented) { // TODO We want to just use detected_info.target but implementing // CPU model & feature detection is todo so here we rely on LLVM. - std.debug.print("CPU features detection is not yet available for this system without LLVM extensions\n", .{}); - process.exit(1); + fatal("CPU features detection is not yet available for this system without LLVM extensions", .{}); + } + + if (target_info.target.os.tag != .freestanding) { + if (ensure_libc_on_non_freestanding) + have_libc = true; + if (ensure_libcpp_on_non_freestanding) + have_libcpp = true; + } + + // Now that we have target info, we can find out if any of the system libraries + // are part of libc or libc++. We remove them from the list and communicate their + // existence via flags instead. + { + var i: usize = 0; + while (i < system_libs.items.len) { + const lib_name = system_libs.items[i]; + if (is_libc_lib_name(target_info.target, lib_name)) { + have_libc = true; + _ = system_libs.orderedRemove(i); + continue; + } + if (is_libcpp_lib_name(target_info.target, lib_name)) { + have_libcpp = true; + _ = system_libs.orderedRemove(i); + continue; + } + i += 1; + } + } + + if (cross_target.isNativeOs() and (system_libs.items.len != 0 or want_native_include_dirs)) { + const paths = std.zig.system.NativePaths.detect(arena) catch |err| { + fatal("unable to detect native system paths: {}", .{@errorName(err)}); + }; + for (paths.warnings.items) |warning| { + warn("{}", .{warning}); + } + try clang_argv.ensureCapacity(clang_argv.items.len + paths.include_dirs.items.len * 2); + for (paths.include_dirs.items) |include_dir| { + clang_argv.appendAssumeCapacity("-isystem"); + clang_argv.appendAssumeCapacity(include_dir); + } + for (paths.lib_dirs.items) |lib_dir| { + try lib_dirs.append(lib_dir); + } + for (paths.rpaths.items) |rpath| { + try rpath_list.append(rpath); + } + } + + if (system_libs.items.len != 0) { + fatal("linking against system libraries not yet supported", .{}); } const src_path = root_src_file orelse { - std.debug.print("expected at least one file argument", .{}); - process.exit(1); + fatal("expected at least one file argument", .{}); }; const object_format: ?std.Target.ObjectFormat = blk: { @@ -461,15 +888,13 @@ fn buildOutputType( } else if (mem.eql(u8, ofmt, "raw")) { break :blk .raw; } else { - std.debug.print("unsupported object format: {}", .{ofmt}); - process.exit(1); + fatal("unsupported object format: {}", .{ofmt}); } }; const bin_path = switch (emit_bin) { .no => { - std.debug.print("-fno-emit-bin not supported yet", .{}); - process.exit(1); + fatal("-fno-emit-bin not supported yet", .{}); }, .yes_default_path => if (object_format != null and object_format.? == .c) try std.fmt.allocPrint(arena, "{}.c", .{root_name}) @@ -515,6 +940,11 @@ fn buildOutputType( try updateModule(gpa, &module, zir_out_path); + if (build_options.have_llvm and only_pp_or_asm) { + // this may include dumping the output to stdout + fatal("TODO: implement `zig cc` when using it as a preprocessor", .{}); + } + while (watch) { try stderr.print("🦎 ", .{}); if (output_mode == .Exe) { @@ -562,7 +992,7 @@ fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !vo }); } } else { - std.log.scoped(.compiler).info("Update completed in {} ms\n", .{update_nanos / std.time.ns_per_ms}); + info("Update completed in {} ms", .{update_nanos / std.time.ns_per_ms}); } if (zir_out_path) |zop| { @@ -631,8 +1061,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { process.exit(0); } else if (mem.eql(u8, arg, "--color")) { if (i + 1 >= args.len) { - std.debug.print("expected [auto|on|off] after --color\n", .{}); - process.exit(1); + fatal("expected [auto|on|off] after --color", .{}); } i += 1; const next_arg = args[i]; @@ -643,16 +1072,14 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { } else if (mem.eql(u8, next_arg, "off")) { color = .Off; } else { - std.debug.print("expected [auto|on|off] after --color, found '{}'\n", .{next_arg}); - process.exit(1); + fatal("expected [auto|on|off] after --color, found '{}'", .{next_arg}); } } else if (mem.eql(u8, arg, "--stdin")) { stdin_flag = true; } else if (mem.eql(u8, arg, "--check")) { check_flag = true; } else { - std.debug.print("unrecognized parameter: '{}'", .{arg}); - process.exit(1); + fatal("unrecognized parameter: '{}'", .{arg}); } } else { try input_files.append(arg); @@ -662,8 +1089,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { if (stdin_flag) { if (input_files.items.len != 0) { - std.debug.print("cannot use --stdin with positional arguments\n", .{}); - process.exit(1); + fatal("cannot use --stdin with positional arguments", .{}); } const stdin = io.getStdIn().inStream(); @@ -672,8 +1098,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { defer gpa.free(source_code); const tree = std.zig.parse(gpa, source_code) catch |err| { - std.debug.print("error parsing stdin: {}\n", .{err}); - process.exit(1); + fatal("error parsing stdin: {}", .{err}); }; defer tree.deinit(); @@ -695,8 +1120,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { } if (input_files.items.len == 0) { - std.debug.print("expected at least one source file argument\n", .{}); - process.exit(1); + fatal("expected at least one source file argument", .{}); } var fmt = Fmt{ @@ -712,8 +1136,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { for (input_files.span()) |file_path| { // Get the real path here to avoid Windows failing on relative file paths with . or .. in them. const real_path = fs.realpathAlloc(gpa, file_path) catch |err| { - std.debug.print("unable to open '{}': {}\n", .{ file_path, err }); - process.exit(1); + fatal("unable to open '{}': {}", .{ file_path, err }); }; defer gpa.free(real_path); @@ -752,7 +1175,7 @@ fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_ fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) { error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path), else => { - std.debug.print("unable to format '{}': {}\n", .{ file_path, err }); + warn("unable to format '{}': {}", .{ file_path, err }); fmt.any_error = true; return; }, @@ -783,7 +1206,7 @@ fn fmtPathDir( try fmtPathDir(fmt, full_path, check_mode, dir, entry.name); } else { fmtPathFile(fmt, full_path, check_mode, dir, entry.name) catch |err| { - std.debug.print("unable to format '{}': {}\n", .{ full_path, err }); + warn("unable to format '{}': {}", .{ full_path, err }); fmt.any_error = true; return; }; @@ -841,6 +1264,7 @@ fn fmtPathFile( if (check_mode) { const anything_changed = try std.zig.render(fmt.gpa, io.null_out_stream, tree); if (anything_changed) { + // TODO this should output to stdout instead of stderr. std.debug.print("{}\n", .{file_path}); fmt.any_error = true; } @@ -858,6 +1282,7 @@ fn fmtPathFile( try af.file.writeAll(fmt.out_buffer.items); try af.finish(); + // TODO this should output to stdout instead of stderr. std.debug.print("{}\n", .{file_path}); } } @@ -925,3 +1350,350 @@ pub const info_zen = \\ \\ ; + +const FileExt = enum { + c, + cpp, + h, + ll, + bc, + assembly, + unknown, +}; + +fn hasCExt(filename: []const u8) bool { + return mem.endsWith(u8, filename, ".c"); +} + +fn hasCppExt(filename: []const u8) bool { + return mem.endsWith(u8, filename, ".C") or + mem.endsWith(u8, filename, ".cc") or + mem.endsWith(u8, filename, ".cpp") or + mem.endsWith(u8, filename, ".cxx"); +} + +fn hasAsmExt(filename: []const u8) bool { + return mem.endsWith(u8, filename, ".s") or mem.endsWith(u8, filename, ".S"); +} + +fn classify_file_ext(filename: []const u8) FileExt { + if (hasCExt(filename)) { + return .c; + } else if (hasCppExt(filename)) { + return .cpp; + } else if (mem.endsWith(u8, filename, ".ll")) { + return .ll; + } else if (mem.endsWith(u8, filename, ".bc")) { + return .bc; + } else if (hasAsmExt(filename)) { + return .assembly; + } else if (mem.endsWith(u8, filename, ".h")) { + return .h; + } else { + // TODO look for .so, .so.X, .so.X.Y, .so.X.Y.Z + return .unknown; + } +} + +extern "c" fn ZigClang_main(argc: c_int, argv: [*:null]?[*:0]u8) c_int; + +/// TODO make it so the return value can be !noreturn +fn punt_to_clang(arena: *Allocator, args: []const []const u8) error{OutOfMemory} { + // Convert the args to the format Clang expects. + const argv = try arena.alloc(?[*:0]u8, args.len + 1); + for (args) |arg, i| { + argv[i] = try arena.dupeZ(u8, arg); // TODO If there was an argsAllocZ we could avoid this allocation. + } + argv[args.len] = null; + const exit_code = ZigClang_main(@intCast(c_int, args.len), argv[0..args.len :null].ptr); + process.exit(@bitCast(u8, @truncate(i8, exit_code))); +} + +const clang_args = @import("clang_options.zig").list; + +pub const ClangArgIterator = struct { + has_next: bool, + zig_equivalent: ZigEquivalent, + only_arg: []const u8, + second_arg: []const u8, + other_args: []const []const u8, + argv: []const []const u8, + next_index: usize, + root_args: ?*Args, + allocator: *Allocator, + + pub const ZigEquivalent = enum { + target, + o, + c, + other, + positional, + l, + ignore, + driver_punt, + pic, + no_pic, + nostdlib, + nostdlib_cpp, + shared, + rdynamic, + wl, + pp_or_asm, + optimize, + debug, + sanitize, + linker_script, + verbose_cmds, + for_linker, + linker_input_z, + lib_dir, + mcpu, + dep_file, + framework_dir, + framework, + nostdlibinc, + }; + + const Args = struct { + next_index: usize, + argv: []const []const u8, + }; + + fn init(allocator: *Allocator, argv: []const []const u8) ClangArgIterator { + return .{ + .next_index = 2, // `zig cc foo` this points to `foo` + .has_next = argv.len > 2, + .zig_equivalent = undefined, + .only_arg = undefined, + .second_arg = undefined, + .other_args = undefined, + .argv = argv, + .root_args = null, + .allocator = allocator, + }; + } + + fn next(self: *ClangArgIterator) !void { + assert(self.has_next); + assert(self.next_index < self.argv.len); + // In this state we know that the parameter we are looking at is a root parameter + // rather than an argument to a parameter. + // We adjust the len below when necessary. + self.other_args = (self.argv.ptr + self.next_index)[0..1]; + var arg = mem.span(self.argv[self.next_index]); + self.incrementArgIndex(); + + if (mem.startsWith(u8, arg, "@")) { + if (self.root_args != null) return error.NestedResponseFile; + + // This is a "compiler response file". We must parse the file and treat its + // contents as command line parameters. + const allocator = self.allocator; + const max_bytes = 10 * 1024 * 1024; // 10 MiB of command line arguments is a reasonable limit + const resp_file_path = arg[1..]; + const resp_contents = fs.cwd().readFileAlloc(allocator, resp_file_path, max_bytes) catch |err| { + fatal("unable to read response file '{}': {}", .{ resp_file_path, @errorName(err) }); + }; + defer allocator.free(resp_contents); + // TODO is there a specification for this file format? Let's find it and make this parsing more robust + // at the very least I'm guessing this needs to handle quotes and `#` comments. + var it = mem.tokenize(resp_contents, " \t\r\n"); + var resp_arg_list = std.ArrayList([]const u8).init(allocator); + defer resp_arg_list.deinit(); + { + errdefer { + for (resp_arg_list.span()) |item| { + allocator.free(mem.span(item)); + } + } + while (it.next()) |token| { + const dupe_token = try mem.dupeZ(allocator, u8, token); + errdefer allocator.free(dupe_token); + try resp_arg_list.append(dupe_token); + } + const args = try allocator.create(Args); + errdefer allocator.destroy(args); + args.* = .{ + .next_index = self.next_index, + .argv = self.argv, + }; + self.root_args = args; + } + const resp_arg_slice = resp_arg_list.toOwnedSlice(); + self.next_index = 0; + self.argv = resp_arg_slice; + + if (resp_arg_slice.len == 0) { + self.resolveRespFileArgs(); + return; + } + + self.has_next = true; + self.other_args = (self.argv.ptr + self.next_index)[0..1]; // We adjust len below when necessary. + arg = mem.span(self.argv[self.next_index]); + self.incrementArgIndex(); + } + if (!mem.startsWith(u8, arg, "-")) { + self.zig_equivalent = .positional; + self.only_arg = arg; + return; + } + + find_clang_arg: for (clang_args) |clang_arg| switch (clang_arg.syntax) { + .flag => { + const prefix_len = clang_arg.matchEql(arg); + if (prefix_len > 0) { + self.zig_equivalent = clang_arg.zig_equivalent; + self.only_arg = arg[prefix_len..]; + + break :find_clang_arg; + } + }, + .joined, .comma_joined => { + // joined example: --target=foo + // comma_joined example: -Wl,-soname,libsoundio.so.2 + const prefix_len = clang_arg.matchStartsWith(arg); + if (prefix_len != 0) { + self.zig_equivalent = clang_arg.zig_equivalent; + self.only_arg = arg[prefix_len..]; // This will skip over the "--target=" part. + + break :find_clang_arg; + } + }, + .joined_or_separate => { + // Examples: `-lfoo`, `-l foo` + const prefix_len = clang_arg.matchStartsWith(arg); + if (prefix_len == arg.len) { + if (self.next_index >= self.argv.len) { + fatal("Expected parameter after '{}'", .{arg}); + } + self.only_arg = self.argv[self.next_index]; + self.incrementArgIndex(); + self.other_args.len += 1; + self.zig_equivalent = clang_arg.zig_equivalent; + + break :find_clang_arg; + } else if (prefix_len != 0) { + self.zig_equivalent = clang_arg.zig_equivalent; + self.only_arg = arg[prefix_len..]; + + break :find_clang_arg; + } + }, + .joined_and_separate => { + // Example: `-Xopenmp-target=riscv64-linux-unknown foo` + const prefix_len = clang_arg.matchStartsWith(arg); + if (prefix_len != 0) { + self.only_arg = arg[prefix_len..]; + if (self.next_index >= self.argv.len) { + fatal("Expected parameter after '{}'", .{arg}); + } + self.second_arg = self.argv[self.next_index]; + self.incrementArgIndex(); + self.other_args.len += 1; + self.zig_equivalent = clang_arg.zig_equivalent; + break :find_clang_arg; + } + }, + .separate => if (clang_arg.matchEql(arg) > 0) { + if (self.next_index >= self.argv.len) { + fatal("Expected parameter after '{}'", .{arg}); + } + self.only_arg = self.argv[self.next_index]; + self.incrementArgIndex(); + self.other_args.len += 1; + self.zig_equivalent = clang_arg.zig_equivalent; + break :find_clang_arg; + }, + .remaining_args_joined => { + const prefix_len = clang_arg.matchStartsWith(arg); + if (prefix_len != 0) { + @panic("TODO"); + } + }, + .multi_arg => if (clang_arg.matchEql(arg) > 0) { + @panic("TODO"); + }, + } + else { + fatal("Unknown Clang option: '{}'", .{arg}); + } + } + + fn incrementArgIndex(self: *ClangArgIterator) void { + self.next_index += 1; + self.resolveRespFileArgs(); + } + + fn resolveRespFileArgs(self: *ClangArgIterator) void { + const allocator = self.allocator; + if (self.next_index >= self.argv.len) { + if (self.root_args) |root_args| { + self.next_index = root_args.next_index; + self.argv = root_args.argv; + + allocator.destroy(root_args); + self.root_args = null; + } + if (self.next_index >= self.argv.len) { + self.has_next = false; + } + } + } +}; + +fn eqlIgnoreCase(ignore_case: bool, a: []const u8, b: []const u8) bool { + if (ignore_case) { + return std.ascii.eqlIgnoreCase(a, b); + } else { + return mem.eql(u8, a, b); + } +} + +fn is_libc_lib_name(target: std.Target, name: []const u8) bool { + const ignore_case = target.os.tag.isDarwin() or target.os.tag == .windows; + + if (eqlIgnoreCase(ignore_case, name, "c")) + return true; + + if (target.isMinGW()) { + if (eqlIgnoreCase(ignore_case, name, "m")) + return true; + + return false; + } + + if (target.abi.isGnu() or target.abi.isMusl() or target.os.tag.isDarwin()) { + if (eqlIgnoreCase(ignore_case, name, "m")) + return true; + if (eqlIgnoreCase(ignore_case, name, "rt")) + return true; + if (eqlIgnoreCase(ignore_case, name, "pthread")) + return true; + if (eqlIgnoreCase(ignore_case, name, "crypt")) + return true; + if (eqlIgnoreCase(ignore_case, name, "util")) + return true; + if (eqlIgnoreCase(ignore_case, name, "xnet")) + return true; + if (eqlIgnoreCase(ignore_case, name, "resolv")) + return true; + if (eqlIgnoreCase(ignore_case, name, "dl")) + return true; + if (eqlIgnoreCase(ignore_case, name, "util")) + return true; + } + + if (target.os.tag.isDarwin() and eqlIgnoreCase(ignore_case, name, "System")) + return true; + + return false; +} + +fn is_libcpp_lib_name(target: std.Target, name: []const u8) bool { + const ignore_case = target.os.tag.isDarwin() or target.os.tag == .windows; + + return eqlIgnoreCase(ignore_case, name, "c++") or + eqlIgnoreCase(ignore_case, name, "stdc++") or + eqlIgnoreCase(ignore_case, name, "c++abi"); +} diff --git a/src-self-hosted/stage2.zig b/src-self-hosted/stage2.zig index 45b8ad3073..a9a9496b94 100644 --- a/src-self-hosted/stage2.zig +++ b/src-self-hosted/stage2.zig @@ -414,6 +414,23 @@ export fn stage2_env(argc: c_int, argv: [*]const [*:0]const u8) c_int { return 0; } +export fn stage2_cc(argc: c_int, argv: [*]const [*:0]const u8, is_cpp: bool) c_int { + const allocator = std.heap.c_allocator; + + var args_list = argvToArrayList(allocator, argc, argv) catch |err| { + std.debug.print("unable to parse arguments: {}\n", .{@errorName(err)}); + return -1; + }; + defer args_list.deinit(); + + self_hosted_main.buildOutputType(allocator, allocator, args_list.items, if (is_cpp) .cpp else .cc) catch |err| { + std.debug.print("zig cc failure: {}\n", .{@errorName(err)}); + return -1; + }; + + return 0; +} + // ABI warning export fn stage2_cmd_targets( zig_triple: ?[*:0]const u8, @@ -1038,267 +1055,4 @@ fn convertSlice(slice: [][:0]u8, ptr: *[*][*:0]u8, len: *usize) !void { ptr.* = new_slice.ptr; } -const clang_args = @import("clang_options.zig").list; - -// ABI warning -pub const ClangArgIterator = extern struct { - has_next: bool, - zig_equivalent: ZigEquivalent, - only_arg: [*:0]const u8, - second_arg: [*:0]const u8, - other_args_ptr: [*]const [*:0]const u8, - other_args_len: usize, - argv_ptr: [*]const [*:0]const u8, - argv_len: usize, - next_index: usize, - root_args: ?*Args, - - // ABI warning - pub const ZigEquivalent = extern enum { - target, - o, - c, - other, - positional, - l, - ignore, - driver_punt, - pic, - no_pic, - nostdlib, - nostdlib_cpp, - shared, - rdynamic, - wl, - pp_or_asm, - optimize, - debug, - sanitize, - linker_script, - verbose_cmds, - for_linker, - linker_input_z, - lib_dir, - mcpu, - dep_file, - framework_dir, - framework, - nostdlibinc, - }; - - const Args = struct { - next_index: usize, - argv_ptr: [*]const [*:0]const u8, - argv_len: usize, - }; - - pub fn init(argv: []const [*:0]const u8) ClangArgIterator { - return .{ - .next_index = 2, // `zig cc foo` this points to `foo` - .has_next = argv.len > 2, - .zig_equivalent = undefined, - .only_arg = undefined, - .second_arg = undefined, - .other_args_ptr = undefined, - .other_args_len = undefined, - .argv_ptr = argv.ptr, - .argv_len = argv.len, - .root_args = null, - }; - } - - pub fn next(self: *ClangArgIterator) !void { - assert(self.has_next); - assert(self.next_index < self.argv_len); - // In this state we know that the parameter we are looking at is a root parameter - // rather than an argument to a parameter. - self.other_args_ptr = self.argv_ptr + self.next_index; - self.other_args_len = 1; // We adjust this value below when necessary. - var arg = mem.span(self.argv_ptr[self.next_index]); - self.incrementArgIndex(); - - if (mem.startsWith(u8, arg, "@")) { - if (self.root_args != null) return error.NestedResponseFile; - - // This is a "compiler response file". We must parse the file and treat its - // contents as command line parameters. - const allocator = std.heap.c_allocator; - const max_bytes = 10 * 1024 * 1024; // 10 MiB of command line arguments is a reasonable limit - const resp_file_path = arg[1..]; - const resp_contents = fs.cwd().readFileAlloc(allocator, resp_file_path, max_bytes) catch |err| { - std.debug.warn("unable to read response file '{}': {}\n", .{ resp_file_path, @errorName(err) }); - process.exit(1); - }; - defer allocator.free(resp_contents); - // TODO is there a specification for this file format? Let's find it and make this parsing more robust - // at the very least I'm guessing this needs to handle quotes and `#` comments. - var it = mem.tokenize(resp_contents, " \t\r\n"); - var resp_arg_list = std.ArrayList([*:0]const u8).init(allocator); - defer resp_arg_list.deinit(); - { - errdefer { - for (resp_arg_list.span()) |item| { - allocator.free(mem.span(item)); - } - } - while (it.next()) |token| { - const dupe_token = try mem.dupeZ(allocator, u8, token); - errdefer allocator.free(dupe_token); - try resp_arg_list.append(dupe_token); - } - const args = try allocator.create(Args); - errdefer allocator.destroy(args); - args.* = .{ - .next_index = self.next_index, - .argv_ptr = self.argv_ptr, - .argv_len = self.argv_len, - }; - self.root_args = args; - } - const resp_arg_slice = resp_arg_list.toOwnedSlice(); - self.next_index = 0; - self.argv_ptr = resp_arg_slice.ptr; - self.argv_len = resp_arg_slice.len; - - if (resp_arg_slice.len == 0) { - self.resolveRespFileArgs(); - return; - } - - self.has_next = true; - self.other_args_ptr = self.argv_ptr + self.next_index; - self.other_args_len = 1; // We adjust this value below when necessary. - arg = mem.span(self.argv_ptr[self.next_index]); - self.incrementArgIndex(); - } - if (!mem.startsWith(u8, arg, "-")) { - self.zig_equivalent = .positional; - self.only_arg = arg.ptr; - return; - } - - find_clang_arg: for (clang_args) |clang_arg| switch (clang_arg.syntax) { - .flag => { - const prefix_len = clang_arg.matchEql(arg); - if (prefix_len > 0) { - self.zig_equivalent = clang_arg.zig_equivalent; - self.only_arg = arg.ptr + prefix_len; - - break :find_clang_arg; - } - }, - .joined, .comma_joined => { - // joined example: --target=foo - // comma_joined example: -Wl,-soname,libsoundio.so.2 - const prefix_len = clang_arg.matchStartsWith(arg); - if (prefix_len != 0) { - self.zig_equivalent = clang_arg.zig_equivalent; - self.only_arg = arg.ptr + prefix_len; // This will skip over the "--target=" part. - - break :find_clang_arg; - } - }, - .joined_or_separate => { - // Examples: `-lfoo`, `-l foo` - const prefix_len = clang_arg.matchStartsWith(arg); - if (prefix_len == arg.len) { - if (self.next_index >= self.argv_len) { - std.debug.warn("Expected parameter after '{}'\n", .{arg}); - process.exit(1); - } - self.only_arg = self.argv_ptr[self.next_index]; - self.incrementArgIndex(); - self.other_args_len += 1; - self.zig_equivalent = clang_arg.zig_equivalent; - - break :find_clang_arg; - } else if (prefix_len != 0) { - self.zig_equivalent = clang_arg.zig_equivalent; - self.only_arg = arg.ptr + prefix_len; - - break :find_clang_arg; - } - }, - .joined_and_separate => { - // Example: `-Xopenmp-target=riscv64-linux-unknown foo` - const prefix_len = clang_arg.matchStartsWith(arg); - if (prefix_len != 0) { - self.only_arg = arg.ptr + prefix_len; - if (self.next_index >= self.argv_len) { - std.debug.warn("Expected parameter after '{}'\n", .{arg}); - process.exit(1); - } - self.second_arg = self.argv_ptr[self.next_index]; - self.incrementArgIndex(); - self.other_args_len += 1; - self.zig_equivalent = clang_arg.zig_equivalent; - break :find_clang_arg; - } - }, - .separate => if (clang_arg.matchEql(arg) > 0) { - if (self.next_index >= self.argv_len) { - std.debug.warn("Expected parameter after '{}'\n", .{arg}); - process.exit(1); - } - self.only_arg = self.argv_ptr[self.next_index]; - self.incrementArgIndex(); - self.other_args_len += 1; - self.zig_equivalent = clang_arg.zig_equivalent; - break :find_clang_arg; - }, - .remaining_args_joined => { - const prefix_len = clang_arg.matchStartsWith(arg); - if (prefix_len != 0) { - @panic("TODO"); - } - }, - .multi_arg => if (clang_arg.matchEql(arg) > 0) { - @panic("TODO"); - }, - } - else { - std.debug.warn("Unknown Clang option: '{}'\n", .{arg}); - process.exit(1); - } - } - - fn incrementArgIndex(self: *ClangArgIterator) void { - self.next_index += 1; - self.resolveRespFileArgs(); - } - - fn resolveRespFileArgs(self: *ClangArgIterator) void { - const allocator = std.heap.c_allocator; - if (self.next_index >= self.argv_len) { - if (self.root_args) |root_args| { - self.next_index = root_args.next_index; - self.argv_ptr = root_args.argv_ptr; - self.argv_len = root_args.argv_len; - - allocator.destroy(root_args); - self.root_args = null; - } - if (self.next_index >= self.argv_len) { - self.has_next = false; - } - } - } -}; - -export fn stage2_clang_arg_iterator( - result: *ClangArgIterator, - argc: usize, - argv: [*]const [*:0]const u8, -) void { - result.* = ClangArgIterator.init(argv[0..argc]); -} - -export fn stage2_clang_arg_next(it: *ClangArgIterator) Error { - it.next() catch |err| switch (err) { - error.NestedResponseFile => return .NestedResponseFile, - error.OutOfMemory => return .OutOfMemory, - }; - return .None; -} - export const stage2_is_zig0 = false; |
