-
-
Save Cloudef/b8c056ee6aff55aac5b5266e88876e76 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
const builtin = @import("builtin"); | |
const log = std.log.scoped(.runtime); | |
fn setenv(key: []const u8, value: []const u8) !void { | |
// Potential footgun here? | |
// It is possible to do a version that does not rely on std.os.environ at all using prctl and reading /proc/self/environ. | |
// https://github.com/ziglang/zig/issues/4524 | |
for (std.os.environ) |*kv| { | |
var token = std.mem.splitScalar(u8, std.mem.span(kv.*), '='); | |
const env_key = token.first(); | |
if (std.mem.eql(u8, env_key, key)) { | |
const allocator = if (builtin.link_libc) std.heap.c_allocator else std.heap.page_allocator; | |
// XXX: non c_allocator is leaky | |
if (builtin.link_libc) try allocator.free(kv.*); | |
var buf = try allocator.allocSentinel(u8, key.len + value.len + 1, 0); | |
@memcpy(buf[0..key.len], key[0..]); | |
buf[key.len] = '='; | |
@memcpy(buf[key.len + 1..], value[0..]); | |
kv.* = buf; | |
return; | |
} | |
} | |
} | |
const LinuxOptions = struct { | |
sonames: []const []const u8 = &.{}, | |
}; | |
pub const OsRelease = struct { | |
// Only because BoundedArray has no comptime len field or const | |
// and @sizeOf(bounded.buffer) apparently isn't comptime O_o | |
const bufsz = 4096; | |
bounded: std.BoundedArray(u8, bufsz) = .{}, | |
bug_report_url: ?[]const u8 = null, | |
build_id: ?[]const u8 = null, | |
documentation_url: ?[]const u8 = null, | |
home_url: ?[]const u8 = null, | |
id: ?[]const u8 = null, | |
logo: ?[]const u8 = null, | |
name: ?[]const u8 = null, | |
pretty_name: ?[]const u8 = null, | |
support_url: ?[]const u8 = null, | |
version: ?[]const u8 = null, | |
version_codename: ?[]const u8 = null, | |
version_id: ?[]const u8 = null, | |
pub fn init() ?@This() { | |
var this: @This() = .{}; | |
{ | |
const f = std.fs.openFileAbsolute("/etc/os-release", .{.mode = .read_only}) catch return null; | |
defer f.close(); | |
f.reader().readIntoBoundedBytes(bufsz, &this.bounded) catch return null; | |
} | |
var tokens = std.mem.splitAny(u8, this.bounded.constSlice(), "=\n"); | |
while (tokens.next()) |key| if (tokens.next()) |value| { | |
inline for (std.meta.fields(@This())) |f| { | |
if (comptime !std.mem.eql(u8, f.name, "bounded")) { | |
comptime var upper: [f.name.len]u8 = undefined; | |
_ = comptime std.ascii.upperString(&upper, f.name); | |
if (std.mem.eql(u8, upper[0..], key)) { | |
const stripped = if (value.len > 0 and value[0] == '"') value[1..value.len - 1] else value; | |
@field(this, f.name) = stripped; | |
std.log.debug("{s} = {s}", .{key, stripped}); | |
} | |
} | |
} | |
}; | |
return this; | |
} | |
}; | |
const Distro = enum { | |
nixos, | |
guix, | |
gobolinux, | |
other, | |
}; | |
// https://old.reddit.com/r/linuxquestions/comments/62g28n/deleted_by_user/dfmjht6/ | |
fn detectDistro() Distro { | |
if (std.os.getenv("DISTRO_OVERRIDE")) |env| { | |
return std.meta.stringToEnum(Distro, env) orelse .other; | |
} | |
if (OsRelease.init()) |distro| { | |
if (distro.id) |id| { | |
std.log.info("Detected Linux distribution: {s}", .{ | |
distro.pretty_name orelse distro.name orelse id | |
}); | |
if (std.meta.stringToEnum(Distro, id)) |d| return d; | |
} | |
} | |
if (std.fs.accessAbsolute("/run/current-system/nixos-version", .{.mode = .read_only})) { | |
std.log.info("Detected Linux distribution: NixOS", .{}); | |
return .nixos; | |
} else |_| {} | |
if (std.fs.accessAbsolute("/etc/GoboLinuxVersion", .{.mode = .read_only})) { | |
std.log.info("Detected Linux distribution: GoboLinux", .{}); | |
return .gobolinux; | |
} else |_| {} | |
std.log.info("Unknown Linux distribution", .{}); | |
return .other; | |
} | |
fn setupLinux(allocator: std.mem.Allocator, comptime opts: LinuxOptions) !void { | |
switch (detectDistro()) { | |
.nixos => { | |
// packages that match soname, don't have to be included | |
const map = std.comptime_string_map.ComptimeStringMap([]const u8, .{ | |
.{ "libvulkan", "vulkan-loader" }, | |
.{ "libGL", "libglvnd" }, | |
.{ "libEGL", "libglvnd" }, | |
.{ "libGLdispatch", "libglvnd" }, | |
.{ "libGLESv1_CM", "libglvnd" }, | |
.{ "libGLESv2", "libglvnd" }, | |
.{ "libGLX", "libglvnd" }, | |
.{ "libOSMesa", "mesa" }, | |
.{ "libOpenGL", "libglvnd" }, | |
.{ "libX11-xcb", "libX11" }, | |
.{ "libwayland-client", "wayland" }, | |
.{ "libwayland-cursor", "wayland" }, | |
.{ "libwayland-server", "wayland" }, | |
.{ "libwayland-egl", "wayland" }, | |
.{ "libdecor-0", "libdecor" }, | |
.{ "libgamemode", "gamemode" }, | |
}); | |
var env = std.ArrayListUnmanaged(u8){}; | |
defer env.deinit(allocator); | |
var env_writer = env.writer(allocator); | |
// get relevant nix store paths | |
const store = blk: { | |
const cmd = "nix-store -q --tree /run/current-system | grep -o '/nix/store/[^ ]*' | sort -u"; | |
const rr = try std.process.Child.run(.{ | |
.allocator = allocator, | |
.argv = &.{ "sh", "-c", cmd }, | |
.max_output_bytes = std.math.maxInt(usize) / 2, | |
}); | |
defer allocator.free(rr.stderr); | |
break :blk rr.stdout; | |
}; | |
defer allocator.free(store); | |
// this part most likely can be reused for guix and gobolinux | |
var tmp = std.BoundedArray(u8, 1024){}; | |
inline for (opts.sonames) |soname| { | |
comptime var split = std.mem.splitScalar(u8, soname, '.'); | |
const base = comptime split.first(); | |
const pkg = comptime map.get(base) orelse base; | |
const needle = std.fmt.comptimePrint("-{s}-", .{pkg}); | |
var found_any = false; | |
var paths = std.mem.tokenizeScalar(u8, store, '\n'); | |
while (paths.next()) |path| { | |
if (std.mem.count(u8, path, needle) == 0) continue; | |
try tmp.resize(0); | |
try tmp.writer().print("{s}/lib", .{path}); | |
if (std.mem.count(u8, env.items, tmp.constSlice()) > 0) { | |
// duplicate | |
found_any = true; | |
continue; | |
} | |
if (std.fs.accessAbsolute(tmp.constSlice(), .{.mode = .read_only})) { | |
if (env.items.len == 0) { | |
// append to the LD_LIBRARY_PATH rather than prepend, so we don't mess with user's own preferences | |
if (std.os.getenv("LD_LIBRARY_PATH")) |path0| { | |
try env_writer.writeAll(path0); | |
} | |
} | |
try env_writer.print("{s}{s}/lib", .{ if (env.items.len > 0) ":" else "", path }); | |
found_any = true; | |
} else |_| {} | |
} | |
if (!found_any) { | |
log.warn("missing library: {s}", .{soname}); | |
} | |
} | |
if (env.items.len > 0) { | |
try setenv("LD_LIBRARY_PATH", env.items); | |
} | |
}, | |
.guix => std.log.warn("guix is untested: things may not work", .{}), | |
.gobolinux => std.log.warn("gobolinux is untested: things may not work", .{}), | |
.other => {}, | |
} | |
} | |
const Options = struct { | |
linux: LinuxOptions = .{}, | |
}; | |
pub fn setup(allocator: std.mem.Allocator, comptime opts: Options) void { | |
switch (builtin.os.tag) { | |
.linux => setupLinux(allocator, opts.linux) catch |err| { | |
log.warn("{}: runtime is incomplete and the program may not function properly", .{err}); | |
}, | |
else => {}, | |
} | |
} | |
pub fn main() !void { | |
setup(std.heap.page_allocator, .{ | |
.linux = .{ | |
.sonames = &.{ | |
"libGL.so", | |
"libEGL.so", | |
"libOSMesa.so.8", | |
"libXext.so.6", | |
"libXi.so.6", | |
"libOSMesa.so.6", | |
"libX11.so.6", | |
"libXrandr.so.2", | |
"libGLESv2.so.2", | |
"libXcursor.so.1", | |
"libXrender.so.1", | |
"libvulkan.so.1", | |
"libXxf86vm.so.1", | |
"libwayland-egl.so.1", | |
"libX11-xcb.so.1", | |
"libXinerama.so.1", | |
"libGLES_CM.so.1", | |
"libGLESv1_CM.so.1", | |
"libGL.so.1", | |
"libEGL.so.1", | |
"libwayland-client.so.0", | |
"libwayland-cursor.so.0", | |
"libxkbcommon.so.0", | |
"libGLX.so.0", | |
"libOpenGL.so.0", | |
"libdecor-0.so.0", | |
"libgamemode.so.0", | |
}, | |
} | |
}); | |
if (std.os.getenv("LD_LIBRARY_PATH")) |path0| log.warn("{s}", .{path0}); | |
var args = try std.process.argsAlloc(std.heap.page_allocator); | |
defer std.process.argsFree(std.heap.page_allocator, args); | |
_ = try std.process.Child.run(.{ .allocator = std.heap.page_allocator, .argv = args[1..] }); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment