Skip to content

Commit 3bd20cd

Browse files
authored
Merge pull request #8 from kubkon/handle-args
Implement different zld drivers via symlinks and refactor usage/options per driver
2 parents 9dcaee1 + ad30320 commit 3bd20cd

20 files changed

+1162
-622
lines changed

README.md

+7-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ You will need latest Zig in your path. You can get nightly binaries from [here](
1212
$ zig build
1313
```
1414

15-
This will create the `zld` binary in `zig-out/bin/zld`. You can then use it like you'd use a standard linker.
15+
This will create the `ld.zld` (Elf), `ld64.zld` (MachO) and `link-zld` (Coff) binaries in `zig-out/bin/`.
16+
You can then use it like you'd use a standard linker.
1617

1718
```
1819
$ cat <<EOF > hello.c
@@ -30,8 +31,11 @@ $ clang -c hello.c
3031
# Or, create .o using zig cc
3132
$ zig cc -c hello.c
3233
33-
# Link away!
34-
$ ./zig-out/bin/zld hello.o -o hello
34+
# On macOS
35+
$ ./zig-out/bin/ld64.zld hello.o -o hello
36+
37+
# On Linux
38+
$ ./zig-out/bin/ld.zld hello.o -o hello
3539
3640
# Run!
3741
$ ./hello

build.zig

+59-17
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
const Builder = @import("std").build.Builder;
1+
const std = @import("std");
2+
const fs = std.fs;
3+
const log = std.log;
4+
5+
const Allocator = std.mem.Allocator;
6+
const Builder = std.build.Builder;
7+
const FileSource = std.build.FileSource;
8+
const LibExeObjStep = std.build.LibExeObjStep;
9+
const InstallDir = std.build.InstallDir;
10+
const Step = std.build.Step;
211

312
pub fn build(b: *Builder) void {
4-
// Standard target options allows the person running `zig build` to choose
5-
// what target to build for. Here we do not override the defaults, which
6-
// means any target is allowed, and the default is native. Other options
7-
// for restricting supported target set are available.
813
const target = b.standardTargetOptions(.{});
9-
10-
// Standard release options allow the person running `zig build` to select
11-
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
1214
const mode = b.standardReleaseOptions();
1315

1416
const enable_logging = b.option(bool, "log", "Whether to enable logging") orelse false;
@@ -27,17 +29,13 @@ pub fn build(b: *Builder) void {
2729
const exe_opts = b.addOptions();
2830
exe.addOptions("build_options", exe_opts);
2931
exe_opts.addOption(bool, "enable_logging", enable_logging);
30-
3132
exe.install();
3233

33-
const run_cmd = exe.run();
34-
run_cmd.step.dependOn(b.getInstallStep());
35-
if (b.args) |args| {
36-
run_cmd.addArgs(args);
37-
}
34+
const elf_symlink = symlink(exe, "ld.zld");
35+
elf_symlink.step.dependOn(&exe.step);
3836

39-
const run_step = b.step("run", "Run the app");
40-
run_step.dependOn(&run_cmd.step);
37+
const macho_symlink = symlink(exe, "ld64.zld");
38+
macho_symlink.step.dependOn(&exe.step);
4139

4240
const tests = b.addTest("src/test.zig");
4341
tests.setBuildMode(mode);
@@ -47,8 +45,52 @@ pub fn build(b: *Builder) void {
4745
const test_opts = b.addOptions();
4846
tests.addOptions("build_options", test_opts);
4947
test_opts.addOption(bool, "enable_qemu", is_qemu_enabled);
48+
test_opts.addOption(bool, "enable_logging", enable_logging);
5049

5150
const test_step = b.step("test", "Run library and end-to-end tests");
52-
test_step.dependOn(&exe.step);
5351
test_step.dependOn(&tests.step);
5452
}
53+
54+
fn symlink(exe: *LibExeObjStep, name: []const u8) *CreateSymlinkStep {
55+
const step = CreateSymlinkStep.create(exe.builder, exe.getOutputSource(), name);
56+
exe.builder.getInstallStep().dependOn(&step.step);
57+
return step;
58+
}
59+
60+
const CreateSymlinkStep = struct {
61+
pub const base_id = .custom;
62+
63+
step: Step,
64+
builder: *Builder,
65+
source: FileSource,
66+
target: []const u8,
67+
68+
pub fn create(
69+
builder: *Builder,
70+
source: FileSource,
71+
target: []const u8,
72+
) *CreateSymlinkStep {
73+
const self = builder.allocator.create(CreateSymlinkStep) catch unreachable;
74+
self.* = CreateSymlinkStep{
75+
.builder = builder,
76+
.step = Step.init(.log, builder.fmt("symlink {s} -> {s}", .{
77+
source.getDisplayName(),
78+
target,
79+
}), builder.allocator, make),
80+
.source = source,
81+
.target = builder.dupe(target),
82+
};
83+
return self;
84+
}
85+
86+
fn make(step: *Step) anyerror!void {
87+
const self = @fieldParentPtr(CreateSymlinkStep, "step", step);
88+
const rel_source = fs.path.basename(self.source.getPath(self.builder));
89+
const source_path = self.builder.getInstallPath(.bin, rel_source);
90+
const target_path = self.builder.getInstallPath(.bin, self.target);
91+
fs.atomicSymLink(self.builder.allocator, source_path, target_path) catch |err| {
92+
log.err("Unable to symlink {s} -> {s}", .{ source_path, target_path });
93+
return err;
94+
};
95+
}
96+
};

src/Coff.zig

+9-9
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,17 @@ const mem = std.mem;
1010

1111
const Allocator = mem.Allocator;
1212
const Object = @import("Coff/Object.zig");
13+
pub const Options = @import("Coff/Options.zig");
1314
const Zld = @import("Zld.zig");
1415

1516
pub const base_tag = Zld.Tag.coff;
1617

1718
base: Zld,
19+
options: Options,
1820

1921
objects: std.ArrayListUnmanaged(Object) = .{},
2022

21-
pub fn openPath(allocator: Allocator, options: Zld.Options) !*Coff {
23+
pub fn openPath(allocator: Allocator, options: Options) !*Coff {
2224
const file = try options.emit.directory.createFile(options.emit.sub_path, .{
2325
.truncate = true,
2426
.read = true,
@@ -34,32 +36,30 @@ pub fn openPath(allocator: Allocator, options: Zld.Options) !*Coff {
3436
return self;
3537
}
3638

37-
fn createEmpty(gpa: Allocator, options: Zld.Options) !*Coff {
39+
fn createEmpty(gpa: Allocator, options: Options) !*Coff {
3840
const self = try gpa.create(Coff);
3941

4042
self.* = .{
4143
.base = .{
4244
.tag = .coff,
43-
.options = options,
4445
.allocator = gpa,
4546
.file = undefined,
4647
},
48+
.options = options,
4749
};
4850

4951
return self;
5052
}
5153

5254
pub fn deinit(self: *Coff) void {
53-
self.closeFiles();
54-
5555
for (self.objects.items) |*object| {
5656
object.deinit(self.base.allocator);
5757
}
5858

5959
self.objects.deinit(self.base.allocator);
6060
}
6161

62-
pub fn closeFiles(self: Coff) void {
62+
pub fn closeFiles(self: *const Coff) void {
6363
for (self.objects.items) |object| {
6464
object.file.close();
6565
}
@@ -70,9 +70,9 @@ pub fn flush(self: *Coff) !void {
7070

7171
var positionals = std.ArrayList([]const u8).init(gpa);
7272
defer positionals.deinit();
73-
try positionals.ensureTotalCapacity(self.base.options.positionals.len);
73+
try positionals.ensureTotalCapacity(self.options.positionals.len);
7474

75-
for (self.base.options.positionals) |obj| {
75+
for (self.options.positionals) |obj| {
7676
positionals.appendAssumeCapacity(obj.path);
7777
}
7878

@@ -110,7 +110,7 @@ fn parseObject(self: *Coff, path: []const u8) !bool {
110110
.file = file,
111111
};
112112

113-
object.parse(self.base.allocator, self.base.options.target) catch |err| switch (err) {
113+
object.parse(self.base.allocator, self.options.target.cpu_arch.?) catch |err| switch (err) {
114114
error.EndOfStream => {
115115
object.deinit(self.base.allocator);
116116
return false;

src/Coff/Object.zig

+7-7
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,10 @@ pub const IMAGE_SYM_CLASS_SECTION = 104;
9696
pub const IMAGE_SYM_CLASS_WEAK_EXTERNAL = 105;
9797
pub const IMAGE_SYM_CLASS_CLR_TOKEN = 107;
9898

99-
// comptime {
100-
// assert(@sizeOf(Symbol) == 18);
101-
// assert(@sizeOf(CoffHeader) == 20);
102-
// }
99+
comptime {
100+
assert(@sizeOf(Symbol) == 18);
101+
assert(@sizeOf(CoffHeader) == 20);
102+
}
103103

104104
pub fn deinit(self: *Object, allocator: Allocator) void {
105105
self.symtab.deinit(allocator);
@@ -108,7 +108,7 @@ pub fn deinit(self: *Object, allocator: Allocator) void {
108108
allocator.free(self.name);
109109
}
110110

111-
pub fn parse(self: *Object, allocator: Allocator, target: std.Target) !void {
111+
pub fn parse(self: *Object, allocator: Allocator, cpu_arch: std.Target.Cpu.Arch) !void {
112112
const reader = self.file.reader();
113113
const header = try reader.readStruct(CoffHeader);
114114

@@ -117,10 +117,10 @@ pub fn parse(self: *Object, allocator: Allocator, target: std.Target) !void {
117117
return error.NotObject;
118118
}
119119

120-
if (header.machine != @enumToInt(target.cpu.arch.toCoffMachine())) {
120+
if (header.machine != @enumToInt(cpu_arch.toCoffMachine())) {
121121
log.debug("Invalid architecture {any}, expected {any}", .{
122122
header.machine,
123-
target.cpu.arch.toCoffMachine(),
123+
cpu_arch.toCoffMachine(),
124124
});
125125
return error.InvalidCpuArch;
126126
}

src/Coff/Options.zig

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
const Options = @This();
2+
3+
const std = @import("std");
4+
const builtin = @import("builtin");
5+
const io = std.io;
6+
const mem = std.mem;
7+
const process = std.process;
8+
9+
const Allocator = mem.Allocator;
10+
const CrossTarget = std.zig.CrossTarget;
11+
const Coff = @import("../Coff.zig");
12+
const Zld = @import("../Zld.zig");
13+
14+
const usage =
15+
\\Usage: link-zld [files...]
16+
\\
17+
\\General Options:
18+
\\-l[name] Specify library to link against
19+
\\-L[path] Specify library search dir
20+
\\-o [path] Specify output path for the final artifact
21+
\\-h, --help Print this help and exit
22+
\\--debug-log [scope] Turn on debugging logs for [scope] (requires zld compiled with -Dlog)
23+
;
24+
25+
emit: Zld.Emit,
26+
output_mode: Zld.OutputMode,
27+
target: CrossTarget,
28+
positionals: []const Zld.LinkObject,
29+
libs: std.StringArrayHashMap(Zld.SystemLib),
30+
lib_dirs: []const []const u8,
31+
32+
pub fn parseArgs(arena: Allocator, ctx: Zld.MainCtx) !Options {
33+
if (ctx.args.len == 0) {
34+
ctx.printSuccess("{s}", .{usage});
35+
}
36+
37+
var positionals = std.ArrayList(Zld.LinkObject).init(arena);
38+
var libs = std.StringArrayHashMap(Zld.SystemLib).init(arena);
39+
var lib_dirs = std.ArrayList([]const u8).init(arena);
40+
var out_path: ?[]const u8 = null;
41+
42+
const Iterator = struct {
43+
args: []const []const u8,
44+
i: usize = 0,
45+
fn next(it: *@This()) ?[]const u8 {
46+
if (it.i >= it.args.len) {
47+
return null;
48+
}
49+
defer it.i += 1;
50+
return it.args[it.i];
51+
}
52+
};
53+
var args_iter = Iterator{ .args = ctx.args };
54+
55+
while (args_iter.next()) |arg| {
56+
if (mem.eql(u8, arg, "--help") or mem.eql(u8, arg, "-h")) {
57+
ctx.printSuccess("{s}", .{usage});
58+
} else if (mem.eql(u8, arg, "--debug-log")) {
59+
const scope = args_iter.next() orelse ctx.printFailure("Expected log scope after {s}", .{arg});
60+
try ctx.log_scopes.append(scope);
61+
} else if (mem.startsWith(u8, arg, "-l")) {
62+
try libs.put(arg[2..], .{});
63+
} else if (mem.startsWith(u8, arg, "-L")) {
64+
try lib_dirs.append(arg[2..]);
65+
} else if (mem.eql(u8, arg, "-o")) {
66+
out_path = args_iter.next() orelse
67+
ctx.printFailure("Expected output path after {s}", .{arg});
68+
} else {
69+
try positionals.append(.{
70+
.path = arg,
71+
.must_link = true,
72+
});
73+
}
74+
}
75+
76+
if (positionals.items.len == 0) {
77+
ctx.printFailure("Expected at least one input .o file", .{});
78+
}
79+
80+
return Options{
81+
.emit = .{
82+
.directory = std.fs.cwd(),
83+
.sub_path = out_path orelse "a.out",
84+
},
85+
.target = CrossTarget.fromTarget(builtin.target),
86+
.output_mode = .exe,
87+
.positionals = positionals.items,
88+
.libs = libs,
89+
.lib_dirs = lib_dirs.items,
90+
};
91+
}

0 commit comments

Comments
 (0)