1const std = @import("../../std.zig");
2const builtin = @import("builtin");
3const ArrayList = std.ArrayList;
4const Allocator = std.mem.Allocator;
5const process = std.process;
6const mem = std.mem;
7
8const NativePaths = @This();
9const NativeTargetInfo = std.zig.system.NativeTargetInfo;
10
11include_dirs: ArrayList([:0]u8),
12lib_dirs: ArrayList([:0]u8),
13framework_dirs: ArrayList([:0]u8),
14rpaths: ArrayList([:0]u8),
15warnings: ArrayList([:0]u8),
16
17pub fn detect(allocator: Allocator, native_info: NativeTargetInfo) !NativePaths {
18    const native_target = native_info.target;
19
20    var self: NativePaths = .{
21        .include_dirs = ArrayList([:0]u8).init(allocator),
22        .lib_dirs = ArrayList([:0]u8).init(allocator),
23        .framework_dirs = ArrayList([:0]u8).init(allocator),
24        .rpaths = ArrayList([:0]u8).init(allocator),
25        .warnings = ArrayList([:0]u8).init(allocator),
26    };
27    errdefer self.deinit();
28
29    var is_nix = false;
30    if (process.getEnvVarOwned(allocator, "NIX_CFLAGS_COMPILE")) |nix_cflags_compile| {
31        defer allocator.free(nix_cflags_compile);
32
33        is_nix = true;
34        var it = mem.tokenize(u8, nix_cflags_compile, " ");
35        while (true) {
36            const word = it.next() orelse break;
37            if (mem.eql(u8, word, "-isystem")) {
38                const include_path = it.next() orelse {
39                    try self.addWarning("Expected argument after -isystem in NIX_CFLAGS_COMPILE");
40                    break;
41                };
42                try self.addIncludeDir(include_path);
43            } else {
44                if (mem.startsWith(u8, word, "-frandom-seed=")) {
45                    continue;
46                }
47                try self.addWarningFmt("Unrecognized C flag from NIX_CFLAGS_COMPILE: {s}", .{word});
48            }
49        }
50    } else |err| switch (err) {
51        error.InvalidUtf8 => {},
52        error.EnvironmentVariableNotFound => {},
53        error.OutOfMemory => |e| return e,
54    }
55    if (process.getEnvVarOwned(allocator, "NIX_LDFLAGS")) |nix_ldflags| {
56        defer allocator.free(nix_ldflags);
57
58        is_nix = true;
59        var it = mem.tokenize(u8, nix_ldflags, " ");
60        while (true) {
61            const word = it.next() orelse break;
62            if (mem.eql(u8, word, "-rpath")) {
63                const rpath = it.next() orelse {
64                    try self.addWarning("Expected argument after -rpath in NIX_LDFLAGS");
65                    break;
66                };
67                try self.addRPath(rpath);
68            } else if (word.len > 2 and word[0] == '-' and word[1] == 'L') {
69                const lib_path = word[2..];
70                try self.addLibDir(lib_path);
71            } else {
72                try self.addWarningFmt("Unrecognized C flag from NIX_LDFLAGS: {s}", .{word});
73                break;
74            }
75        }
76    } else |err| switch (err) {
77        error.InvalidUtf8 => {},
78        error.EnvironmentVariableNotFound => {},
79        error.OutOfMemory => |e| return e,
80    }
81    if (is_nix) {
82        return self;
83    }
84
85    if (comptime builtin.target.isDarwin()) {
86        try self.addIncludeDir("/usr/include");
87        try self.addIncludeDir("/usr/local/include");
88
89        try self.addLibDir("/usr/lib");
90        try self.addLibDir("/usr/local/lib");
91
92        try self.addFrameworkDir("/Library/Frameworks");
93        try self.addFrameworkDir("/System/Library/Frameworks");
94
95        return self;
96    }
97
98    if (comptime native_target.os.tag == .solaris) {
99        try self.addLibDir("/usr/lib/64");
100        try self.addLibDir("/usr/local/lib/64");
101        try self.addLibDir("/lib/64");
102
103        try self.addIncludeDir("/usr/include");
104        try self.addIncludeDir("/usr/local/include");
105
106        return self;
107    }
108
109    if (native_target.os.tag != .windows) {
110        const triple = try native_target.linuxTriple(allocator);
111        const qual = native_target.cpu.arch.ptrBitWidth();
112
113        // TODO: $ ld --verbose | grep SEARCH_DIR
114        // the output contains some paths that end with lib64, maybe include them too?
115        // TODO: what is the best possible order of things?
116        // TODO: some of these are suspect and should only be added on some systems. audit needed.
117
118        try self.addIncludeDir("/usr/local/include");
119        try self.addLibDirFmt("/usr/local/lib{d}", .{qual});
120        try self.addLibDir("/usr/local/lib");
121
122        try self.addIncludeDirFmt("/usr/include/{s}", .{triple});
123        try self.addLibDirFmt("/usr/lib/{s}", .{triple});
124
125        try self.addIncludeDir("/usr/include");
126        try self.addLibDirFmt("/lib{d}", .{qual});
127        try self.addLibDir("/lib");
128        try self.addLibDirFmt("/usr/lib{d}", .{qual});
129        try self.addLibDir("/usr/lib");
130
131        // example: on a 64-bit debian-based linux distro, with zlib installed from apt:
132        // zlib.h is in /usr/include (added above)
133        // libz.so.1 is in /lib/x86_64-linux-gnu (added here)
134        try self.addLibDirFmt("/lib/{s}", .{triple});
135    }
136
137    return self;
138}
139
140pub fn deinit(self: *NativePaths) void {
141    deinitArray(&self.include_dirs);
142    deinitArray(&self.lib_dirs);
143    deinitArray(&self.framework_dirs);
144    deinitArray(&self.rpaths);
145    deinitArray(&self.warnings);
146    self.* = undefined;
147}
148
149fn deinitArray(array: *ArrayList([:0]u8)) void {
150    for (array.items) |item| {
151        array.allocator.free(item);
152    }
153    array.deinit();
154}
155
156pub fn addIncludeDir(self: *NativePaths, s: []const u8) !void {
157    return self.appendArray(&self.include_dirs, s);
158}
159
160pub fn addIncludeDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
161    const item = try std.fmt.allocPrintZ(self.include_dirs.allocator, fmt, args);
162    errdefer self.include_dirs.allocator.free(item);
163    try self.include_dirs.append(item);
164}
165
166pub fn addLibDir(self: *NativePaths, s: []const u8) !void {
167    return self.appendArray(&self.lib_dirs, s);
168}
169
170pub fn addLibDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
171    const item = try std.fmt.allocPrintZ(self.lib_dirs.allocator, fmt, args);
172    errdefer self.lib_dirs.allocator.free(item);
173    try self.lib_dirs.append(item);
174}
175
176pub fn addWarning(self: *NativePaths, s: []const u8) !void {
177    return self.appendArray(&self.warnings, s);
178}
179
180pub fn addFrameworkDir(self: *NativePaths, s: []const u8) !void {
181    return self.appendArray(&self.framework_dirs, s);
182}
183
184pub fn addFrameworkDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
185    const item = try std.fmt.allocPrintZ(self.framework_dirs.allocator, fmt, args);
186    errdefer self.framework_dirs.allocator.free(item);
187    try self.framework_dirs.append(item);
188}
189
190pub fn addWarningFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void {
191    const item = try std.fmt.allocPrintZ(self.warnings.allocator, fmt, args);
192    errdefer self.warnings.allocator.free(item);
193    try self.warnings.append(item);
194}
195
196pub fn addRPath(self: *NativePaths, s: []const u8) !void {
197    return self.appendArray(&self.rpaths, s);
198}
199
200fn appendArray(self: *NativePaths, array: *ArrayList([:0]u8), s: []const u8) !void {
201    _ = self;
202    const item = try array.allocator.dupeZ(u8, s);
203    errdefer array.allocator.free(item);
204    try array.append(item);
205}
206