zig cc improvements

* The generated options data file is sorted now in a way that
   makes sure longer prefixes are first. This prevents collisions
   with some parameters.
 * Add support for `-fPIC`, `-fno-PIC`, `-nostdlib`, `-shared`,
   `-rdynamic`, `-Wl,-soname`, `-Wl,-rpath`
 * Better support for `-o`.
 * Disable generating h files
 * Shared library support.
 * Better positional argument support.
This commit is contained in:
Andrew Kelley 2020-03-21 15:29:52 -04:00
parent a4eaeee720
commit 0eee98edc1
No known key found for this signature in database
GPG Key ID: 7C5F548F728501A9
6 changed files with 1838 additions and 1548 deletions

View File

@ -20,7 +20,7 @@ pub const CliArg = struct {
/// Prefixed by "/"
psl: bool = false,
const Syntax = union(enum) {
pub const Syntax = union(enum) {
/// A flag with no values.
flag,
@ -46,7 +46,7 @@ pub const CliArg = struct {
multi_arg: u8,
};
fn matchEql(self: CliArg, arg: []const u8) bool {
pub fn matchEql(self: CliArg, arg: []const u8) bool {
if (self.pd1 and arg.len >= self.name.len + 1 and
mem.startsWith(u8, arg, "-") and mem.eql(u8, arg[1..], self.name))
{
@ -65,7 +65,7 @@ pub const CliArg = struct {
return false;
}
fn matchStartsWith(self: CliArg, arg: []const u8) usize {
pub fn matchStartsWith(self: CliArg, arg: []const u8) usize {
if (self.pd1 and arg.len >= self.name.len + 1 and
mem.startsWith(u8, arg, "-") and mem.startsWith(u8, arg[1..], self.name))
{

File diff suppressed because it is too large Load Diff

View File

@ -1239,6 +1239,14 @@ pub const ClangArgIterator = extern struct {
other,
positional,
l,
ignore,
passthrough,
pic,
no_pic,
nostdlib,
shared,
rdynamic,
wl,
};
fn init(argv: []const [*:0]const u8) ClangArgIterator {
@ -1282,7 +1290,8 @@ pub const ClangArgIterator = extern struct {
break :find_clang_arg;
},
.joined, .comma_joined => {
// Example: --target=foo
// 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;

View File

@ -430,6 +430,7 @@ static int main0(int argc, char **argv) {
bool enable_dump_analysis = false;
bool enable_doc_generation = false;
bool emit_bin = true;
const char *emit_bin_override_path = nullptr;
bool emit_asm = false;
bool emit_llvm_ir = false;
bool emit_h = false;
@ -451,6 +452,7 @@ static int main0(int argc, char **argv) {
bool function_sections = false;
const char *mcpu = nullptr;
CodeModel code_model = CodeModelDefault;
const char *override_soname = nullptr;
ZigList<const char *> llvm_argv = {0};
llvm_argv.append("zig (LLVM option parsing)");
@ -576,10 +578,14 @@ static int main0(int argc, char **argv) {
} else if (argc >= 2 && strcmp(argv[1], "fmt") == 0) {
return stage2_fmt(argc, argv);
} else if (argc >= 2 && strcmp(argv[1], "cc") == 0) {
const char *o_arg = nullptr;
emit_h = false;
bool c_arg = false;
Stage2ClangArgIterator it;
stage2_clang_arg_iterator(&it, argc, argv);
bool nostdlib = false;
bool is_shared_lib = false;
ZigList<Buf *> linker_args = {};
while (it.has_next) {
if ((err = stage2_clang_arg_next(&it))) {
fprintf(stderr, "unable to parse command line parameters: %s\n", err_str(err));
@ -590,7 +596,8 @@ static int main0(int argc, char **argv) {
target_string = it.only_arg;
break;
case Stage2ClangArgO: // -o
o_arg = it.only_arg;
emit_bin_override_path = it.only_arg;
enable_cache = CacheOptOn;
break;
case Stage2ClangArgC: // -c
c_arg = true;
@ -601,9 +608,17 @@ static int main0(int argc, char **argv) {
}
break;
case Stage2ClangArgPositional: {
CFile *c_file = heap::c_allocator.create<CFile>();
c_file->source_path = it.only_arg;
c_source_files.append(c_file);
Buf *arg_buf = buf_create_from_str(it.only_arg);
if (buf_ends_with_str(arg_buf, ".c") ||
buf_ends_with_str(arg_buf, ".cpp") ||
buf_ends_with_str(arg_buf, ".s"))
{
CFile *c_file = heap::c_allocator.create<CFile>();
c_file->source_path = it.only_arg;
c_source_files.append(c_file);
} else {
objects.append(it.only_arg);
}
break;
}
case Stage2ClangArgL: // -l
@ -611,18 +626,111 @@ static int main0(int argc, char **argv) {
have_libc = true;
link_libs.append(it.only_arg);
break;
case Stage2ClangArgIgnore:
break;
case Stage2ClangArgPassthrough:
// Never mind what we're doing, just pass the args directly. For example --help.
return ZigClang_main(argc, argv);
case Stage2ClangArgPIC:
want_pic = WantPICEnabled;
break;
case Stage2ClangArgNoPIC:
want_pic = WantPICDisabled;
break;
case Stage2ClangArgNoStdLib:
nostdlib = true;
break;
case Stage2ClangArgShared:
is_dynamic = true;
is_shared_lib = true;
break;
case Stage2ClangArgRDynamic:
rdynamic = true;
break;
case Stage2ClangArgWL: {
const char *arg = it.only_arg;
for (;;) {
size_t pos = 0;
while (arg[pos] != ',' && arg[pos] != 0) pos += 1;
linker_args.append(buf_create_from_mem(arg, pos));
if (arg[pos] == 0) break;
arg += pos + 1;
}
break;
}
}
}
// Parse linker args
for (size_t i = 0; i < linker_args.length; i += 1) {
Buf *arg = linker_args.at(i);
if (buf_eql_str(arg, "-soname")) {
i += 1;
if (i >= linker_args.length) {
fprintf(stderr, "expected linker arg after '%s'\n", buf_ptr(arg));
return EXIT_FAILURE;
}
Buf *soname_buf = linker_args.at(i);
override_soname = buf_ptr(soname_buf);
// use it as --name
// example: libsoundio.so.2
size_t prefix = 0;
if (buf_starts_with_str(soname_buf, "lib")) {
prefix = 3;
}
size_t end = buf_len(soname_buf);
if (buf_ends_with_str(soname_buf, ".so")) {
end -= 3;
} else {
bool found_digit = false;
while (end > 0 && isdigit(buf_ptr(soname_buf)[end - 1])) {
found_digit = true;
end -= 1;
}
if (found_digit && end > 0 && buf_ptr(soname_buf)[end - 1] == '.') {
end -= 1;
} else {
end = buf_len(soname_buf);
}
if (buf_ends_with_str(buf_slice(soname_buf, prefix, end), ".so")) {
end -= 3;
}
}
out_name = buf_ptr(buf_slice(soname_buf, prefix, end));
} else if (buf_eql_str(arg, "-rpath")) {
i += 1;
if (i >= linker_args.length) {
fprintf(stderr, "expected linker arg after '%s'\n", buf_ptr(arg));
return EXIT_FAILURE;
}
Buf *rpath = linker_args.at(i);
rpath_list.append(buf_ptr(rpath));
} else {
fprintf(stderr, "warning: unsupported linker arg: %s\n", buf_ptr(arg));
}
}
if (!nostdlib && !have_libc) {
have_libc = true;
link_libs.append("c");
}
if (!c_arg) {
cmd = CmdBuild;
out_type = OutTypeExe;
if (o_arg == nullptr) {
zig_panic("TODO set out name to a.out");
if (is_shared_lib) {
out_type = OutTypeLib;
} else {
out_type = OutTypeExe;
}
if (emit_bin_override_path == nullptr) {
emit_bin_override_path = "a.out";
}
} else {
cmd = CmdBuild;
out_type = OutTypeObj;
}
if (c_source_files.length == 0 && objects.length == 0) {
// For example `zig cc` and no args should print the "no input files" message.
return ZigClang_main(argc, argv);
}
} else for (int i = 1; i < argc; i += 1) {
char *arg = argv[i];
@ -1184,6 +1292,18 @@ static int main0(int argc, char **argv) {
buf_out_name = buf_alloc();
os_path_extname(&basename, buf_out_name, nullptr);
}
if (need_name && buf_out_name == nullptr && objects.length == 1) {
Buf basename = BUF_INIT;
os_path_split(buf_create_from_str(objects.at(0)), nullptr, &basename);
buf_out_name = buf_alloc();
os_path_extname(&basename, buf_out_name, nullptr);
}
if (need_name && buf_out_name == nullptr && emit_bin_override_path != nullptr) {
Buf basename = BUF_INIT;
os_path_split(buf_create_from_str(emit_bin_override_path), nullptr, &basename);
buf_out_name = buf_alloc();
os_path_extname(&basename, buf_out_name, nullptr);
}
if (need_name && buf_out_name == nullptr) {
fprintf(stderr, "--name [name] not provided and unable to infer\n\n");
@ -1259,6 +1379,10 @@ static int main0(int argc, char **argv) {
g->function_sections = function_sections;
g->code_model = code_model;
if (override_soname) {
g->override_soname = buf_create_from_str(override_soname);
}
for (size_t i = 0; i < lib_dirs.length; i += 1) {
codegen_add_lib_dir(g, lib_dirs.at(i));
}
@ -1337,7 +1461,14 @@ static int main0(int argc, char **argv) {
os_spawn_process(args, &term);
return term.code;
} else if (cmd == CmdBuild) {
if (g->enable_cache) {
if (emit_bin_override_path != nullptr) {
Buf *dest_path = buf_create_from_str(emit_bin_override_path);
if ((err = os_update_file(&g->bin_file_output_path, dest_path))) {
fprintf(stderr, "unable to copy %s to %s: %s\n", buf_ptr(&g->bin_file_output_path),
buf_ptr(dest_path), err_str(err));
return main_exit(root_progress_node, EXIT_FAILURE);
}
} else if (g->enable_cache) {
#if defined(ZIG_OS_WINDOWS)
buf_replace(&g->bin_file_output_path, '/', '\\');
buf_replace(g->output_dir, '/', '\\');

View File

@ -325,6 +325,14 @@ enum Stage2ClangArg {
Stage2ClangArgOther,
Stage2ClangArgPositional,
Stage2ClangArgL,
Stage2ClangArgIgnore,
Stage2ClangArgPassthrough,
Stage2ClangArgPIC,
Stage2ClangArgNoPIC,
Stage2ClangArgNoStdLib,
Stage2ClangArgShared,
Stage2ClangArgRDynamic,
Stage2ClangArgWL,
};
// ABI warning

View File

@ -38,6 +38,42 @@ const known_options = [_]KnownOpt{
.name = "l",
.ident = "l",
},
.{
.name = "pipe",
.ident = "ignore",
},
.{
.name = "help",
.ident = "passthrough",
},
.{
.name = "fPIC",
.ident = "pic",
},
.{
.name = "fno-PIC",
.ident = "no_pic",
},
.{
.name = "nostdlib",
.ident = "nostdlib",
},
.{
.name = "no-standard-libraries",
.ident = "nostdlib",
},
.{
.name = "shared",
.ident = "shared",
},
.{
.name = "rdynamic",
.ident = "rdynamic",
},
.{
.name = "Wl,",
.ident = "wl",
},
};
const blacklisted_options = [_][]const u8{};
@ -110,7 +146,7 @@ pub fn main() anyerror!void {
const tree = try parser.parse(json_text);
const root_map = &tree.root.Object;
var all_names = std.ArrayList([]const u8).init(allocator);
var all_objects = std.ArrayList(*json.ObjectMap).init(allocator);
{
var it = root_map.iterator();
it_map: while (it.next()) |kv| {
@ -123,10 +159,12 @@ pub fn main() anyerror!void {
if (std.mem.eql(u8, blacklisted_key, kv.key)) continue :it_map;
}
if (kv.value.Object.get("Name").?.value.String.len == 0) continue;
try all_names.append(kv.key);
try all_objects.append(&kv.value.Object);
}
}
std.sort.sort([]const u8, all_names.span(), nameLessThan);
// Some options have multiple matches. As an example, "-Wl,foo" matches both
// "W" and "Wl,". So we sort this list in order of descending priority.
std.sort.sort(*json.ObjectMap, all_objects.span(), objectLessThan);
var stdout_bos = std.io.bufferedOutStream(std.io.getStdOut().outStream());
const stdout = stdout_bos.outStream();
@ -138,8 +176,7 @@ pub fn main() anyerror!void {
\\
);
for (all_names.span()) |key| {
const obj = &root_map.get(key).?.value.Object;
for (all_objects.span()) |obj| {
const name = obj.get("Name").?.value.String;
var pd1 = false;
var pd2 = false;
@ -153,61 +190,12 @@ pub fn main() anyerror!void {
} else if (std.mem.eql(u8, prefix, "/")) {
pslash = true;
} else {
std.debug.warn("{} (key {}) has unrecognized prefix '{}'\n", .{ name, key, prefix });
std.debug.warn("{} has unrecognized prefix '{}'\n", .{ name, prefix });
std.process.exit(1);
}
}
const num_args = @intCast(u8, obj.get("NumArgs").?.value.Integer);
const syntax_str: []const u8 = blk: {
for (obj.get("!superclasses").?.value.Array.span()) |superclass_json| {
const superclass = superclass_json.String;
if (std.mem.eql(u8, superclass, "Joined")) {
break :blk ".joined";
} else if (std.mem.eql(u8, superclass, "CLJoined")) {
break :blk ".joined";
} else if (std.mem.eql(u8, superclass, "CLIgnoredJoined")) {
break :blk ".joined";
} else if (std.mem.eql(u8, superclass, "CLCompileJoined")) {
break :blk ".joined";
} else if (std.mem.eql(u8, superclass, "JoinedOrSeparate")) {
break :blk ".joined_or_separate";
} else if (std.mem.eql(u8, superclass, "CLJoinedOrSeparate")) {
break :blk ".joined_or_separate";
} else if (std.mem.eql(u8, superclass, "CLCompileJoinedOrSeparate")) {
break :blk ".joined_or_separate";
} else if (std.mem.eql(u8, superclass, "Flag")) {
break :blk ".flag";
} else if (std.mem.eql(u8, superclass, "CLFlag")) {
break :blk ".flag";
} else if (std.mem.eql(u8, superclass, "CLIgnoredFlag")) {
break :blk ".flag";
} else if (std.mem.eql(u8, superclass, "Separate")) {
break :blk ".separate";
} else if (std.mem.eql(u8, superclass, "JoinedAndSeparate")) {
break :blk ".joined_and_separate";
} else if (std.mem.eql(u8, superclass, "CommaJoined")) {
break :blk ".comma_joined";
} else if (std.mem.eql(u8, superclass, "CLRemainingArgsJoined")) {
break :blk ".remaining_args_joined";
} else if (std.mem.eql(u8, superclass, "MultiArg")) {
break :blk try std.fmt.allocPrint(allocator, ".{{ .multi_arg = {} }}", .{num_args});
}
}
if (std.mem.eql(u8, name, "<input>")) {
break :blk ".flag";
} else if (std.mem.eql(u8, name, "<unknown>")) {
break :blk ".flag";
}
const kind_def = obj.get("Kind").?.value.Object.get("def").?.value.String;
if (std.mem.eql(u8, kind_def, "KIND_FLAG")) {
break :blk ".flag";
}
std.debug.warn("{} (key {}) has unrecognized superclasses:\n", .{ name, key });
for (obj.get("!superclasses").?.value.Array.span()) |superclass_json| {
std.debug.warn(" {}\n", .{superclass_json.String});
}
std.process.exit(1);
};
const syntax = objSyntax(obj);
if (knownOption(name)) |ident| {
try stdout.print(
\\.{{
@ -219,22 +207,14 @@ pub fn main() anyerror!void {
\\ .psl = {},
\\}},
\\
, .{ name, syntax_str, ident, pd1, pd2, pslash });
} else if (pd1 and !pd2 and !pslash and
std.mem.eql(u8, syntax_str, ".flag"))
{
, .{ name, syntax, ident, pd1, pd2, pslash });
} else if (pd1 and !pd2 and !pslash and syntax == .flag) {
try stdout.print("flagpd1(\"{}\"),\n", .{name});
} else if (pd1 and !pd2 and !pslash and
std.mem.eql(u8, syntax_str, ".joined"))
{
} else if (pd1 and !pd2 and !pslash and syntax == .joined) {
try stdout.print("joinpd1(\"{}\"),\n", .{name});
} else if (pd1 and !pd2 and !pslash and
std.mem.eql(u8, syntax_str, ".joined_or_separate"))
{
} else if (pd1 and !pd2 and !pslash and syntax == .joined_or_separate) {
try stdout.print("jspd1(\"{}\"),\n", .{name});
} else if (pd1 and !pd2 and !pslash and
std.mem.eql(u8, syntax_str, ".separate"))
{
} else if (pd1 and !pd2 and !pslash and syntax == .separate) {
try stdout.print("sepd1(\"{}\"),\n", .{name});
} else {
try stdout.print(
@ -247,7 +227,7 @@ pub fn main() anyerror!void {
\\ .psl = {},
\\}},
\\
, .{ name, syntax_str, pd1, pd2, pslash });
, .{ name, syntax, pd1, pd2, pslash });
}
}
@ -259,8 +239,142 @@ pub fn main() anyerror!void {
try stdout_bos.flush();
}
fn nameLessThan(a: []const u8, b: []const u8) bool {
return std.mem.lessThan(u8, a, b);
// TODO we should be able to import clang_options.zig but currently this is problematic because it will
// import stage2.zig and that causes a bunch of stuff to get exported
const Syntax = union(enum) {
/// A flag with no values.
flag,
/// An option which prefixes its (single) value.
joined,
/// An option which is followed by its value.
separate,
/// An option which is either joined to its (non-empty) value, or followed by its value.
joined_or_separate,
/// An option which is both joined to its (first) value, and followed by its (second) value.
joined_and_separate,
/// An option followed by its values, which are separated by commas.
comma_joined,
/// An option which consumes an optional joined argument and any other remaining arguments.
remaining_args_joined,
/// An option which is which takes multiple (separate) arguments.
multi_arg: u8,
pub fn format(
self: Syntax,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
out_stream: var,
) !void {
switch (self) {
.multi_arg => |n| return out_stream.print(".{{.{}={}}}", .{ @tagName(self), n }),
else => return out_stream.print(".{}", .{@tagName(self)}),
}
}
};
fn objSyntax(obj: *json.ObjectMap) Syntax {
const num_args = @intCast(u8, obj.get("NumArgs").?.value.Integer);
for (obj.get("!superclasses").?.value.Array.span()) |superclass_json| {
const superclass = superclass_json.String;
if (std.mem.eql(u8, superclass, "Joined")) {
return .joined;
} else if (std.mem.eql(u8, superclass, "CLJoined")) {
return .joined;
} else if (std.mem.eql(u8, superclass, "CLIgnoredJoined")) {
return .joined;
} else if (std.mem.eql(u8, superclass, "CLCompileJoined")) {
return .joined;
} else if (std.mem.eql(u8, superclass, "JoinedOrSeparate")) {
return .joined_or_separate;
} else if (std.mem.eql(u8, superclass, "CLJoinedOrSeparate")) {
return .joined_or_separate;
} else if (std.mem.eql(u8, superclass, "CLCompileJoinedOrSeparate")) {
return .joined_or_separate;
} else if (std.mem.eql(u8, superclass, "Flag")) {
return .flag;
} else if (std.mem.eql(u8, superclass, "CLFlag")) {
return .flag;
} else if (std.mem.eql(u8, superclass, "CLIgnoredFlag")) {
return .flag;
} else if (std.mem.eql(u8, superclass, "Separate")) {
return .separate;
} else if (std.mem.eql(u8, superclass, "JoinedAndSeparate")) {
return .joined_and_separate;
} else if (std.mem.eql(u8, superclass, "CommaJoined")) {
return .comma_joined;
} else if (std.mem.eql(u8, superclass, "CLRemainingArgsJoined")) {
return .remaining_args_joined;
} else if (std.mem.eql(u8, superclass, "MultiArg")) {
return .{ .multi_arg = num_args };
}
}
const name = obj.get("Name").?.value.String;
if (std.mem.eql(u8, name, "<input>")) {
return .flag;
} else if (std.mem.eql(u8, name, "<unknown>")) {
return .flag;
}
const kind_def = obj.get("Kind").?.value.Object.get("def").?.value.String;
if (std.mem.eql(u8, kind_def, "KIND_FLAG")) {
return .flag;
}
const key = obj.get("!name").?.value.String;
std.debug.warn("{} (key {}) has unrecognized superclasses:\n", .{ name, key });
for (obj.get("!superclasses").?.value.Array.span()) |superclass_json| {
std.debug.warn(" {}\n", .{superclass_json.String});
}
std.process.exit(1);
}
fn syntaxMatchesWithEql(syntax: Syntax) bool {
return switch (syntax) {
.flag,
.separate,
.multi_arg,
=> true,
.joined,
.joined_or_separate,
.joined_and_separate,
.comma_joined,
.remaining_args_joined,
=> false,
};
}
fn objectLessThan(a: *json.ObjectMap, b: *json.ObjectMap) bool {
// Priority is determined by exact matches first, followed by prefix matches in descending
// length, with key as a final tiebreaker.
const a_syntax = objSyntax(a);
const b_syntax = objSyntax(b);
const a_match_with_eql = syntaxMatchesWithEql(a_syntax);
const b_match_with_eql = syntaxMatchesWithEql(b_syntax);
if (a_match_with_eql and !b_match_with_eql) {
return true;
} else if (!a_match_with_eql and b_match_with_eql) {
return false;
}
if (!a_match_with_eql and !b_match_with_eql) {
const a_name = a.get("Name").?.value.String;
const b_name = b.get("Name").?.value.String;
if (a_name.len != b_name.len) {
return a_name.len > b_name.len;
}
}
const a_key = a.get("!name").?.value.String;
const b_key = b.get("!name").?.value.String;
return std.mem.lessThan(u8, a_key, b_key);
}
fn usageAndExit(file: fs.File, arg0: []const u8, code: u8) noreturn {