zigup.zig 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089
  1. const std = @import("std");
  2. const builtin = @import("builtin");
  3. const mem = std.mem;
  4. const zip = @import("lib/zip.zig");
  5. const ArrayList = std.ArrayList;
  6. const Allocator = mem.Allocator;
  7. const fixdeletetree = @import("fixdeletetree.zig");
  8. const arch = switch (builtin.cpu.arch) {
  9. .x86_64 => "x86_64",
  10. .aarch64 => "aarch64",
  11. .arm => "armv7a",
  12. .riscv64 => "riscv64",
  13. .powerpc64le => "powerpc64le",
  14. .powerpc => "powerpc",
  15. else => @compileError("Unsupported CPU Architecture"),
  16. };
  17. const os = switch (builtin.os.tag) {
  18. .windows => "windows",
  19. .linux => "linux",
  20. .macos => "macos",
  21. else => @compileError("Unsupported OS"),
  22. };
  23. const url_platform = os ++ "-" ++ arch;
  24. const json_platform = arch ++ "-" ++ os;
  25. const archive_ext = if (builtin.os.tag == .windows) "zip" else "tar.xz";
  26. var global_optional_install_dir: ?[]const u8 = null;
  27. var global_optional_path_link: ?[]const u8 = null;
  28. var global_enable_log = true;
  29. fn loginfo(comptime fmt: []const u8, args: anytype) void {
  30. if (global_enable_log) {
  31. std.debug.print(fmt ++ "\n", args);
  32. }
  33. }
  34. pub fn oom(e: error{OutOfMemory}) noreturn {
  35. @panic(@errorName(e));
  36. }
  37. const DownloadResult = union(enum) {
  38. ok: void,
  39. err: []u8,
  40. pub fn deinit(self: DownloadResult, allocator: Allocator) void {
  41. switch (self) {
  42. .ok => {},
  43. .err => |e| allocator.free(e),
  44. }
  45. }
  46. };
  47. fn download(allocator: Allocator, url: []const u8, writer: anytype) DownloadResult {
  48. const uri = std.Uri.parse(url) catch |err| std.debug.panic(
  49. "failed to parse url '{s}' with {s}", .{url, @errorName(err)}
  50. );
  51. var client = std.http.Client{ .allocator = allocator };
  52. defer client.deinit();
  53. client.initDefaultProxies(allocator) catch |err| return .{ .err = std.fmt.allocPrint(
  54. allocator, "failed to query the HTTP proxy settings with {s}", .{ @errorName(err) }
  55. ) catch |e| oom(e) };
  56. var header_buffer: [4096]u8 = undefined;
  57. var request = client.open(.GET, uri, .{
  58. .server_header_buffer = &header_buffer,
  59. .keep_alive = false,
  60. }) catch |err| return .{ .err = std.fmt.allocPrint(
  61. allocator, "failed to connect to the HTTP server with {s}", .{ @errorName(err) }
  62. ) catch |e| oom(e) };
  63. defer request.deinit();
  64. request.send() catch |err| return .{ .err = std.fmt.allocPrint(
  65. allocator, "failed to send the HTTP request with {s}", .{ @errorName(err) }
  66. ) catch |e| oom(e) };
  67. request.wait() catch |err| return .{ .err = std.fmt.allocPrint(
  68. allocator, "failed to read the HTTP response headers with {s}", .{ @errorName(err) }
  69. ) catch |e| oom(e) };
  70. if (request.response.status != .ok) return .{ .err = std.fmt.allocPrint(
  71. allocator,
  72. "HTTP server replied with unsuccessful response '{d} {s}'",
  73. .{ @intFromEnum(request.response.status), request.response.status.phrase() orelse "" },
  74. ) catch |e| oom(e) };
  75. // TODO: we take advantage of request.response.content_length
  76. var buf: [std.mem.page_size]u8 = undefined;
  77. while (true) {
  78. const len = request.reader().read(&buf) catch |err| return .{ .err = std.fmt.allocPrint(
  79. allocator, "failed to read the HTTP response body with {s}'", .{ @errorName(err) }
  80. ) catch |e| oom(e) };
  81. if (len == 0)
  82. return .ok;
  83. writer.writeAll(buf[0..len]) catch |err| return .{ .err = std.fmt.allocPrint(
  84. allocator, "failed to write the HTTP response body with {s}'", .{ @errorName(err) }
  85. ) catch |e| oom(e) };
  86. }
  87. }
  88. const DownloadStringResult = union(enum) {
  89. ok: []u8,
  90. err: []u8,
  91. };
  92. fn downloadToString(allocator: Allocator, url: []const u8) DownloadStringResult {
  93. var response_array_list = ArrayList(u8).initCapacity(allocator, 20 * 1024) catch |e| oom(e); // 20 KB (modify if response is expected to be bigger)
  94. defer response_array_list.deinit();
  95. switch (download(allocator, url, response_array_list.writer())) {
  96. .ok => return .{ .ok = response_array_list.toOwnedSlice() catch |e| oom(e) },
  97. .err => |e| return .{ .err = e },
  98. }
  99. }
  100. fn ignoreHttpCallback(request: []const u8) void {
  101. _ = request;
  102. }
  103. fn getHomeDir() ![]const u8 {
  104. return std.posix.getenv("HOME") orelse {
  105. std.log.err("cannot find install directory, $HOME environment variable is not set", .{});
  106. return error.MissingHomeEnvironmentVariable;
  107. };
  108. }
  109. fn allocInstallDirString(allocator: Allocator) ![]const u8 {
  110. // TODO: maybe support ZIG_INSTALL_DIR environment variable?
  111. // TODO: maybe support a file on the filesystem to configure install dir?
  112. if (builtin.os.tag == .windows) {
  113. const self_exe_dir = try std.fs.selfExeDirPathAlloc(allocator);
  114. defer allocator.free(self_exe_dir);
  115. return std.fs.path.join(allocator, &.{ self_exe_dir, "zig" });
  116. }
  117. const home = try getHomeDir();
  118. if (!std.fs.path.isAbsolute(home)) {
  119. std.log.err("$HOME environment variable '{s}' is not an absolute path", .{home});
  120. return error.BadHomeEnvironmentVariable;
  121. }
  122. return std.fs.path.join(allocator, &[_][]const u8{ home, "zig" });
  123. }
  124. const GetInstallDirOptions = struct {
  125. create: bool,
  126. };
  127. fn getInstallDir(allocator: Allocator, options: GetInstallDirOptions) ![]const u8 {
  128. var optional_dir_to_free_on_error: ?[]const u8 = null;
  129. errdefer if (optional_dir_to_free_on_error) |dir| allocator.free(dir);
  130. const install_dir = init: {
  131. if (global_optional_install_dir) |dir| break :init dir;
  132. optional_dir_to_free_on_error = try allocInstallDirString(allocator);
  133. break :init optional_dir_to_free_on_error.?;
  134. };
  135. std.debug.assert(std.fs.path.isAbsolute(install_dir));
  136. loginfo("install directory '{s}'", .{install_dir});
  137. if (options.create) {
  138. loggyMakeDirAbsolute(install_dir) catch |e| switch (e) {
  139. error.PathAlreadyExists => {},
  140. else => return e,
  141. };
  142. }
  143. return install_dir;
  144. }
  145. fn makeZigPathLinkString(allocator: Allocator) ![]const u8 {
  146. if (global_optional_path_link) |path| return path;
  147. const zigup_dir = try std.fs.selfExeDirPathAlloc(allocator);
  148. defer allocator.free(zigup_dir);
  149. return try std.fs.path.join(allocator, &[_][]const u8{ zigup_dir, comptime "zig" ++ builtin.target.exeFileExt() });
  150. }
  151. // TODO: this should be in standard lib
  152. fn toAbsolute(allocator: Allocator, path: []const u8) ![]u8 {
  153. std.debug.assert(!std.fs.path.isAbsolute(path));
  154. const cwd = try std.process.getCwdAlloc(allocator);
  155. defer allocator.free(cwd);
  156. return std.fs.path.join(allocator, &[_][]const u8{ cwd, path });
  157. }
  158. fn help() void {
  159. std.io.getStdErr().writeAll(
  160. \\Download and manage zig compilers.
  161. \\
  162. \\Common Usage:
  163. \\
  164. \\ zigup VERSION download and set VERSION compiler as default
  165. \\ zigup fetch VERSION download VERSION compiler
  166. \\ zigup default [VERSION] get or set the default compiler
  167. \\ zigup list list installed compiler versions
  168. \\ zigup clean [VERSION] deletes the given compiler version, otherwise, cleans all compilers
  169. \\ that aren't the default, master, or marked to keep.
  170. \\ zigup keep VERSION mark a compiler to be kept during clean
  171. \\ zigup run VERSION ARGS... run the given VERSION of the compiler with the given ARGS...
  172. \\
  173. \\Uncommon Usage:
  174. \\
  175. \\ zigup fetch-index download and print the download index json
  176. \\
  177. \\Common Options:
  178. \\ --install-dir DIR override the default install location
  179. \\ --path-link PATH path to the `zig` symlink that points to the default compiler
  180. \\ this will typically be a file path within a PATH directory so
  181. \\ that the user can just run `zig`
  182. \\
  183. ) catch unreachable;
  184. }
  185. fn getCmdOpt(args: [][]const u8, i: *usize) ![]const u8 {
  186. i.* += 1;
  187. if (i.* == args.len) {
  188. std.log.err("option '{s}' requires an argument", .{args[i.* - 1]});
  189. return error.AlreadyReported;
  190. }
  191. return args[i.*];
  192. }
  193. pub fn main() !u8 {
  194. return main2() catch |e| switch (e) {
  195. error.AlreadyReported => return 1,
  196. else => return e,
  197. };
  198. }
  199. pub fn main2() !u8 {
  200. if (builtin.os.tag == .windows) {
  201. _ = try std.os.windows.WSAStartup(2, 2);
  202. }
  203. var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
  204. const allocator = arena.allocator();
  205. const args_array = try std.process.argsAlloc(allocator);
  206. // no need to free, os will do it
  207. //defer std.process.argsFree(allocator, argsArray);
  208. var args = if (args_array.len == 0) args_array else args_array[1..];
  209. // parse common options
  210. //
  211. {
  212. var i: usize = 0;
  213. var newlen: usize = 0;
  214. while (i < args.len) : (i += 1) {
  215. const arg = args[i];
  216. if (std.mem.eql(u8, "--install-dir", arg)) {
  217. global_optional_install_dir = try getCmdOpt(args, &i);
  218. if (!std.fs.path.isAbsolute(global_optional_install_dir.?)) {
  219. global_optional_install_dir = try toAbsolute(allocator, global_optional_install_dir.?);
  220. }
  221. } else if (std.mem.eql(u8, "--path-link", arg)) {
  222. global_optional_path_link = try getCmdOpt(args, &i);
  223. if (!std.fs.path.isAbsolute(global_optional_path_link.?)) {
  224. global_optional_path_link = try toAbsolute(allocator, global_optional_path_link.?);
  225. }
  226. } else if (std.mem.eql(u8, "-h", arg) or std.mem.eql(u8, "--help", arg)) {
  227. help();
  228. return 0;
  229. } else {
  230. if (newlen == 0 and std.mem.eql(u8, "run", arg)) {
  231. return try runCompiler(allocator, args[i + 1 ..]);
  232. }
  233. args[newlen] = args[i];
  234. newlen += 1;
  235. }
  236. }
  237. args = args[0..newlen];
  238. }
  239. if (args.len == 0) {
  240. help();
  241. return 1;
  242. }
  243. if (std.mem.eql(u8, "fetch-index", args[0])) {
  244. if (args.len != 1) {
  245. std.log.err("'index' command requires 0 arguments but got {d}", .{args.len - 1});
  246. return 1;
  247. }
  248. var download_index = try fetchDownloadIndex(allocator);
  249. defer download_index.deinit(allocator);
  250. try std.io.getStdOut().writeAll(download_index.text);
  251. return 0;
  252. }
  253. if (std.mem.eql(u8, "fetch", args[0])) {
  254. if (args.len != 2) {
  255. std.log.err("'fetch' command requires 1 argument but got {d}", .{args.len - 1});
  256. return 1;
  257. }
  258. try fetchCompiler(allocator, args[1], .leave_default);
  259. return 0;
  260. }
  261. if (std.mem.eql(u8, "clean", args[0])) {
  262. if (args.len == 1) {
  263. try cleanCompilers(allocator, null);
  264. } else if (args.len == 2) {
  265. try cleanCompilers(allocator, args[1]);
  266. } else {
  267. std.log.err("'clean' command requires 0 or 1 arguments but got {d}", .{args.len - 1});
  268. return 1;
  269. }
  270. return 0;
  271. }
  272. if (std.mem.eql(u8, "keep", args[0])) {
  273. if (args.len != 2) {
  274. std.log.err("'keep' command requires 1 argument but got {d}", .{args.len - 1});
  275. return 1;
  276. }
  277. try keepCompiler(allocator, args[1]);
  278. return 0;
  279. }
  280. if (std.mem.eql(u8, "list", args[0])) {
  281. if (args.len != 1) {
  282. std.log.err("'list' command requires 0 arguments but got {d}", .{args.len - 1});
  283. return 1;
  284. }
  285. try listCompilers(allocator);
  286. return 0;
  287. }
  288. if (std.mem.eql(u8, "default", args[0])) {
  289. if (args.len == 1) {
  290. try printDefaultCompiler(allocator);
  291. return 0;
  292. }
  293. if (args.len == 2) {
  294. const version_string = args[1];
  295. const install_dir_string = try getInstallDir(allocator, .{ .create = true });
  296. defer allocator.free(install_dir_string);
  297. const resolved_version_string = init_resolved: {
  298. if (!std.mem.eql(u8, version_string, "master"))
  299. break :init_resolved version_string;
  300. const optional_master_dir: ?[]const u8 = blk: {
  301. var install_dir = std.fs.openDirAbsolute(install_dir_string, .{ .iterate = true }) catch |e| switch (e) {
  302. error.FileNotFound => break :blk null,
  303. else => return e,
  304. };
  305. defer install_dir.close();
  306. break :blk try getMasterDir(allocator, &install_dir);
  307. };
  308. // no need to free master_dir, this is a short lived program
  309. break :init_resolved optional_master_dir orelse {
  310. std.log.err("master has not been fetched", .{});
  311. return 1;
  312. };
  313. };
  314. const compiler_dir = try std.fs.path.join(allocator, &[_][]const u8{ install_dir_string, resolved_version_string });
  315. defer allocator.free(compiler_dir);
  316. try setDefaultCompiler(allocator, compiler_dir, .verify_existence);
  317. return 0;
  318. }
  319. std.log.err("'default' command requires 1 or 2 arguments but got {d}", .{args.len - 1});
  320. return 1;
  321. }
  322. if (args.len == 1) {
  323. try fetchCompiler(allocator, args[0], .set_default);
  324. return 0;
  325. }
  326. const command = args[0];
  327. args = args[1..];
  328. std.log.err("command not impl '{s}'", .{command});
  329. return 1;
  330. //const optionalInstallPath = try find_zigs(allocator);
  331. }
  332. pub fn runCompiler(allocator: Allocator, args: []const []const u8) !u8 {
  333. // disable log so we don't add extra output to whatever the compiler will output
  334. global_enable_log = false;
  335. if (args.len <= 1) {
  336. std.log.err("zigup run requires at least 2 arguments: zigup run VERSION PROG ARGS...", .{});
  337. return 1;
  338. }
  339. const version_string = args[0];
  340. const install_dir_string = try getInstallDir(allocator, .{ .create = true });
  341. defer allocator.free(install_dir_string);
  342. const compiler_dir = try std.fs.path.join(allocator, &[_][]const u8{ install_dir_string, version_string });
  343. defer allocator.free(compiler_dir);
  344. if (!try existsAbsolute(compiler_dir)) {
  345. std.log.err("compiler '{s}' does not exist, fetch it first with: zigup fetch {0s}", .{version_string});
  346. return 1;
  347. }
  348. var argv = std.ArrayList([]const u8).init(allocator);
  349. try argv.append(try std.fs.path.join(allocator, &.{ compiler_dir, "files", comptime "zig" ++ builtin.target.exeFileExt() }));
  350. try argv.appendSlice(args[1..]);
  351. // TODO: use "execve" if on linux
  352. var proc = std.ChildProcess.init(argv.items, allocator);
  353. const ret_val = try proc.spawnAndWait();
  354. switch (ret_val) {
  355. .Exited => |code| return code,
  356. else => |result| {
  357. std.log.err("compiler exited with {}", .{result});
  358. return 0xff;
  359. },
  360. }
  361. }
  362. const SetDefault = enum { set_default, leave_default };
  363. fn fetchCompiler(allocator: Allocator, version_arg: []const u8, set_default: SetDefault) !void {
  364. const install_dir = try getInstallDir(allocator, .{ .create = true });
  365. defer allocator.free(install_dir);
  366. var optional_download_index: ?DownloadIndex = null;
  367. // This is causing an LLVM error
  368. //defer if (optionalDownloadIndex) |_| optionalDownloadIndex.?.deinit(allocator);
  369. // Also I would rather do this, but it doesn't work because of const issues
  370. //defer if (optionalDownloadIndex) |downloadIndex| downloadIndex.deinit(allocator);
  371. const VersionUrl = struct { version: []const u8, url: []const u8 };
  372. // NOTE: we only fetch the download index if the user wants to download 'master', we can skip
  373. // this step for all other versions because the version to URL mapping is fixed (see getDefaultUrl)
  374. const is_master = std.mem.eql(u8, version_arg, "master");
  375. const version_url = blk: {
  376. if (!is_master)
  377. break :blk VersionUrl{ .version = version_arg, .url = try getDefaultUrl(allocator, version_arg) };
  378. optional_download_index = try fetchDownloadIndex(allocator);
  379. const master = optional_download_index.?.json.value.object.get("master").?;
  380. const compiler_version = master.object.get("version").?.string;
  381. const master_linux = master.object.get(json_platform).?;
  382. const master_linux_tarball = master_linux.object.get("tarball").?.string;
  383. break :blk VersionUrl{ .version = compiler_version, .url = master_linux_tarball };
  384. };
  385. const compiler_dir = try std.fs.path.join(allocator, &[_][]const u8{ install_dir, version_url.version });
  386. defer allocator.free(compiler_dir);
  387. try installCompiler(allocator, compiler_dir, version_url.url);
  388. if (is_master) {
  389. const master_symlink = try std.fs.path.join(allocator, &[_][]const u8{ install_dir, "master" });
  390. defer allocator.free(master_symlink);
  391. if (builtin.os.tag == .windows) {
  392. var file = try std.fs.createFileAbsolute(master_symlink, .{});
  393. defer file.close();
  394. try file.writer().writeAll(version_url.version);
  395. } else {
  396. _ = try loggyUpdateSymlink(version_url.version, master_symlink, .{ .is_directory = true });
  397. }
  398. }
  399. if (set_default == .set_default) {
  400. try setDefaultCompiler(allocator, compiler_dir, .existence_verified);
  401. }
  402. }
  403. const download_index_url = "https://ziglang.org/download/index.json";
  404. const DownloadIndex = struct {
  405. text: []u8,
  406. json: std.json.Parsed(std.json.Value),
  407. pub fn deinit(self: *DownloadIndex, allocator: Allocator) void {
  408. self.json.deinit();
  409. allocator.free(self.text);
  410. }
  411. };
  412. fn fetchDownloadIndex(allocator: Allocator) !DownloadIndex {
  413. const text = switch (downloadToString(allocator, download_index_url)) {
  414. .ok => |text| text,
  415. .err => |err| {
  416. std.log.err("download '{s}' failed: {s}", .{download_index_url, err});
  417. return error.AlreadyReported;
  418. },
  419. };
  420. errdefer allocator.free(text);
  421. var json = try std.json.parseFromSlice(std.json.Value, allocator, text, .{});
  422. errdefer json.deinit();
  423. return DownloadIndex{ .text = text, .json = json };
  424. }
  425. fn loggyMakeDirAbsolute(dir_absolute: []const u8) !void {
  426. if (builtin.os.tag == .windows) {
  427. loginfo("mkdir \"{s}\"", .{dir_absolute});
  428. } else {
  429. loginfo("mkdir '{s}'", .{dir_absolute});
  430. }
  431. try std.fs.makeDirAbsolute(dir_absolute);
  432. }
  433. fn loggyDeleteTreeAbsolute(dir_absolute: []const u8) !void {
  434. if (builtin.os.tag == .windows) {
  435. loginfo("rd /s /q \"{s}\"", .{dir_absolute});
  436. } else {
  437. loginfo("rm -rf '{s}'", .{dir_absolute});
  438. }
  439. try fixdeletetree.deleteTreeAbsolute(dir_absolute);
  440. }
  441. pub fn loggyRenameAbsolute(old_path: []const u8, new_path: []const u8) !void {
  442. loginfo("mv '{s}' '{s}'", .{ old_path, new_path });
  443. try std.fs.renameAbsolute(old_path, new_path);
  444. }
  445. pub fn loggySymlinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags: std.fs.Dir.SymLinkFlags) !void {
  446. loginfo("ln -s '{s}' '{s}'", .{ target_path, sym_link_path });
  447. // NOTE: can't use symLinkAbsolute because it requires target_path to be absolute but we don't want that
  448. // not sure if it is a bug in the standard lib or not
  449. //try std.fs.symLinkAbsolute(target_path, sym_link_path, flags);
  450. _ = flags;
  451. try std.posix.symlink(target_path, sym_link_path);
  452. }
  453. /// returns: true if the symlink was updated, false if it was already set to the given `target_path`
  454. pub fn loggyUpdateSymlink(target_path: []const u8, sym_link_path: []const u8, flags: std.fs.Dir.SymLinkFlags) !bool {
  455. var current_target_path_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
  456. if (std.fs.readLinkAbsolute(sym_link_path, &current_target_path_buffer)) |current_target_path| {
  457. if (std.mem.eql(u8, target_path, current_target_path)) {
  458. loginfo("symlink '{s}' already points to '{s}'", .{ sym_link_path, target_path });
  459. return false; // already up-to-date
  460. }
  461. try std.posix.unlink(sym_link_path);
  462. } else |e| switch (e) {
  463. error.FileNotFound => {},
  464. error.NotLink => {
  465. std.debug.print(
  466. "unable to update/overwrite the 'zig' PATH symlink, the file '{s}' already exists and is not a symlink\n",
  467. .{ sym_link_path},
  468. );
  469. std.process.exit(1);
  470. },
  471. else => return e,
  472. }
  473. try loggySymlinkAbsolute(target_path, sym_link_path, flags);
  474. return true; // updated
  475. }
  476. // TODO: this should be in std lib somewhere
  477. fn existsAbsolute(absolutePath: []const u8) !bool {
  478. std.fs.cwd().access(absolutePath, .{}) catch |e| switch (e) {
  479. error.FileNotFound => return false,
  480. error.PermissionDenied => return e,
  481. error.InputOutput => return e,
  482. error.SystemResources => return e,
  483. error.SymLinkLoop => return e,
  484. error.FileBusy => return e,
  485. error.Unexpected => unreachable,
  486. error.InvalidUtf8 => return e,
  487. error.InvalidWtf8 => return e,
  488. error.ReadOnlyFileSystem => unreachable,
  489. error.NameTooLong => unreachable,
  490. error.BadPathName => unreachable,
  491. };
  492. return true;
  493. }
  494. fn listCompilers(allocator: Allocator) !void {
  495. const install_dir_string = try getInstallDir(allocator, .{ .create = false });
  496. defer allocator.free(install_dir_string);
  497. var install_dir = std.fs.openDirAbsolute(install_dir_string, .{ .iterate = true }) catch |e| switch (e) {
  498. error.FileNotFound => return,
  499. else => return e,
  500. };
  501. defer install_dir.close();
  502. const stdout = std.io.getStdOut().writer();
  503. {
  504. var it = install_dir.iterate();
  505. while (try it.next()) |entry| {
  506. if (entry.kind != .directory)
  507. continue;
  508. if (std.mem.endsWith(u8, entry.name, ".installing"))
  509. continue;
  510. try stdout.print("{s}\n", .{entry.name});
  511. }
  512. }
  513. }
  514. fn keepCompiler(allocator: Allocator, compiler_version: []const u8) !void {
  515. const install_dir_string = try getInstallDir(allocator, .{ .create = true });
  516. defer allocator.free(install_dir_string);
  517. var install_dir = try std.fs.openDirAbsolute(install_dir_string, .{ .iterate = true });
  518. defer install_dir.close();
  519. var compiler_dir = install_dir.openDir(compiler_version, .{}) catch |e| switch (e) {
  520. error.FileNotFound => {
  521. std.log.err("compiler not found: {s}", .{compiler_version});
  522. return error.AlreadyReported;
  523. },
  524. else => return e,
  525. };
  526. var keep_fd = try compiler_dir.createFile("keep", .{});
  527. keep_fd.close();
  528. loginfo("created '{s}{c}{s}{c}{s}'", .{ install_dir_string, std.fs.path.sep, compiler_version, std.fs.path.sep, "keep" });
  529. }
  530. fn cleanCompilers(allocator: Allocator, compiler_name_opt: ?[]const u8) !void {
  531. const install_dir_string = try getInstallDir(allocator, .{ .create = true });
  532. defer allocator.free(install_dir_string);
  533. // getting the current compiler
  534. const default_comp_opt = try getDefaultCompiler(allocator);
  535. defer if (default_comp_opt) |default_compiler| allocator.free(default_compiler);
  536. var install_dir = std.fs.openDirAbsolute(install_dir_string, .{ .iterate = true }) catch |e| switch (e) {
  537. error.FileNotFound => return,
  538. else => return e,
  539. };
  540. defer install_dir.close();
  541. const master_points_to_opt = try getMasterDir(allocator, &install_dir);
  542. defer if (master_points_to_opt) |master_points_to| allocator.free(master_points_to);
  543. if (compiler_name_opt) |compiler_name| {
  544. if (getKeepReason(master_points_to_opt, default_comp_opt, compiler_name)) |reason| {
  545. std.log.err("cannot clean '{s}' ({s})", .{ compiler_name, reason });
  546. return error.AlreadyReported;
  547. }
  548. loginfo("deleting '{s}{c}{s}'", .{ install_dir_string, std.fs.path.sep, compiler_name });
  549. try fixdeletetree.deleteTree(install_dir, compiler_name);
  550. } else {
  551. var it = install_dir.iterate();
  552. while (try it.next()) |entry| {
  553. if (entry.kind != .directory)
  554. continue;
  555. if (getKeepReason(master_points_to_opt, default_comp_opt, entry.name)) |reason| {
  556. loginfo("keeping '{s}' ({s})", .{ entry.name, reason });
  557. continue;
  558. }
  559. {
  560. var compiler_dir = try install_dir.openDir(entry.name, .{});
  561. defer compiler_dir.close();
  562. if (compiler_dir.access("keep", .{})) |_| {
  563. loginfo("keeping '{s}' (has keep file)", .{entry.name});
  564. continue;
  565. } else |e| switch (e) {
  566. error.FileNotFound => {},
  567. else => return e,
  568. }
  569. }
  570. loginfo("deleting '{s}{c}{s}'", .{ install_dir_string, std.fs.path.sep, entry.name });
  571. try fixdeletetree.deleteTree(install_dir, entry.name);
  572. }
  573. }
  574. }
  575. fn readDefaultCompiler(allocator: Allocator, buffer: *[std.fs.MAX_PATH_BYTES + 1]u8) !?[]const u8 {
  576. const path_link = try makeZigPathLinkString(allocator);
  577. defer allocator.free(path_link);
  578. if (builtin.os.tag == .windows) {
  579. var file = std.fs.openFileAbsolute(path_link, .{}) catch |e| switch (e) {
  580. error.FileNotFound => return null,
  581. else => return e,
  582. };
  583. defer file.close();
  584. try file.seekTo(win32exelink.exe_offset);
  585. const len = try file.readAll(buffer);
  586. if (len != buffer.len) {
  587. std.log.err("path link file '{s}' is too small", .{path_link});
  588. return error.AlreadyReported;
  589. }
  590. const target_exe = std.mem.sliceTo(buffer, 0);
  591. return try allocator.dupe(u8, targetPathToVersion(target_exe));
  592. }
  593. const target_path = std.fs.readLinkAbsolute(path_link, buffer[0..std.fs.MAX_PATH_BYTES]) catch |e| switch (e) {
  594. error.FileNotFound => return null,
  595. else => return e,
  596. };
  597. defer allocator.free(target_path);
  598. return try allocator.dupe(u8, targetPathToVersion(target_path));
  599. }
  600. fn targetPathToVersion(target_path: []const u8) []const u8 {
  601. return std.fs.path.basename(std.fs.path.dirname(std.fs.path.dirname(target_path).?).?);
  602. }
  603. fn readMasterDir(buffer: *[std.fs.MAX_PATH_BYTES]u8, install_dir: *std.fs.Dir) !?[]const u8 {
  604. if (builtin.os.tag == .windows) {
  605. var file = install_dir.openFile("master", .{}) catch |e| switch (e) {
  606. error.FileNotFound => return null,
  607. else => return e,
  608. };
  609. defer file.close();
  610. return buffer[0..try file.readAll(buffer)];
  611. }
  612. return install_dir.readLink("master", buffer) catch |e| switch (e) {
  613. error.FileNotFound => return null,
  614. else => return e,
  615. };
  616. }
  617. fn getDefaultCompiler(allocator: Allocator) !?[]const u8 {
  618. var buffer: [std.fs.MAX_PATH_BYTES + 1]u8 = undefined;
  619. const slice_path = (try readDefaultCompiler(allocator, &buffer)) orelse return null;
  620. const path_to_return = try allocator.alloc(u8, slice_path.len);
  621. @memcpy(path_to_return, slice_path);
  622. return path_to_return;
  623. }
  624. fn getMasterDir(allocator: Allocator, install_dir: *std.fs.Dir) !?[]const u8 {
  625. var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
  626. const slice_path = (try readMasterDir(&buffer, install_dir)) orelse return null;
  627. const path_to_return = try allocator.alloc(u8, slice_path.len);
  628. @memcpy(path_to_return, slice_path);
  629. return path_to_return;
  630. }
  631. fn printDefaultCompiler(allocator: Allocator) !void {
  632. const default_compiler_opt = try getDefaultCompiler(allocator);
  633. defer if (default_compiler_opt) |default_compiler| allocator.free(default_compiler);
  634. const stdout = std.io.getStdOut().writer();
  635. if (default_compiler_opt) |default_compiler| {
  636. try stdout.print("{s}\n", .{default_compiler});
  637. } else {
  638. try stdout.writeAll("<no-default>\n");
  639. }
  640. }
  641. const ExistVerify = enum { existence_verified, verify_existence };
  642. fn setDefaultCompiler(allocator: Allocator, compiler_dir: []const u8, exist_verify: ExistVerify) !void {
  643. switch (exist_verify) {
  644. .existence_verified => {},
  645. .verify_existence => {
  646. var dir = std.fs.openDirAbsolute(compiler_dir, .{}) catch |err| switch (err) {
  647. error.FileNotFound => {
  648. std.log.err("compiler '{s}' is not installed", .{std.fs.path.basename(compiler_dir)});
  649. return error.AlreadyReported;
  650. },
  651. else => |e| return e,
  652. };
  653. dir.close();
  654. },
  655. }
  656. const path_link = try makeZigPathLinkString(allocator);
  657. defer allocator.free(path_link);
  658. const link_target = try std.fs.path.join(allocator, &[_][]const u8{ compiler_dir, "files", comptime "zig" ++ builtin.target.exeFileExt() });
  659. defer allocator.free(link_target);
  660. if (builtin.os.tag == .windows) {
  661. try createExeLink(link_target, path_link);
  662. } else {
  663. _ = try loggyUpdateSymlink(link_target, path_link, .{});
  664. }
  665. try verifyPathLink(allocator, path_link);
  666. }
  667. /// Verify that path_link will work. It verifies that `path_link` is
  668. /// in PATH and there is no zig executable in an earlier directory in PATH.
  669. fn verifyPathLink(allocator: Allocator, path_link: []const u8) !void {
  670. const path_link_dir = std.fs.path.dirname(path_link) orelse {
  671. std.log.err("invalid '--path-link' '{s}', it must be a file (not the root directory)", .{path_link});
  672. return error.AlreadyReported;
  673. };
  674. const path_link_dir_id = blk: {
  675. var dir = std.fs.openDirAbsolute(path_link_dir, .{}) catch |err| {
  676. std.log.err("unable to open the path-link directory '{s}': {s}", .{ path_link_dir, @errorName(err) });
  677. return error.AlreadyReported;
  678. };
  679. defer dir.close();
  680. break :blk try FileId.initFromDir(dir, path_link);
  681. };
  682. if (builtin.os.tag == .windows) {
  683. const path_env = std.process.getEnvVarOwned(allocator, "PATH") catch |err| switch (err) {
  684. error.EnvironmentVariableNotFound => return,
  685. else => |e| return e,
  686. };
  687. defer allocator.free(path_env);
  688. var free_pathext: ?[]const u8 = null;
  689. defer if (free_pathext) |p| allocator.free(p);
  690. const pathext_env = blk: {
  691. if (std.process.getEnvVarOwned(allocator, "PATHEXT")) |env| {
  692. free_pathext = env;
  693. break :blk env;
  694. } else |err| switch (err) {
  695. error.EnvironmentVariableNotFound => break :blk "",
  696. else => |e| return e,
  697. }
  698. break :blk "";
  699. };
  700. var path_it = std.mem.tokenize(u8, path_env, ";");
  701. while (path_it.next()) |path| {
  702. switch (try compareDir(path_link_dir_id, path)) {
  703. .missing => continue,
  704. // can't be the same directory because we were able to open and get
  705. // the file id for path_link_dir_id
  706. .access_denied => {},
  707. .match => return,
  708. .mismatch => {},
  709. }
  710. {
  711. const exe = try std.fs.path.join(allocator, &.{ path, "zig" });
  712. defer allocator.free(exe);
  713. try enforceNoZig(path_link, exe);
  714. }
  715. var ext_it = std.mem.tokenize(u8, pathext_env, ";");
  716. while (ext_it.next()) |ext| {
  717. if (ext.len == 0) continue;
  718. const basename = try std.mem.concat(allocator, u8, &.{ "zig", ext });
  719. defer allocator.free(basename);
  720. const exe = try std.fs.path.join(allocator, &.{ path, basename });
  721. defer allocator.free(exe);
  722. try enforceNoZig(path_link, exe);
  723. }
  724. }
  725. } else {
  726. var path_it = std.mem.tokenize(u8, std.posix.getenv("PATH") orelse "", ":");
  727. while (path_it.next()) |path| {
  728. switch (try compareDir(path_link_dir_id, path)) {
  729. .missing => continue,
  730. // can't be the same directory because we were able to open and get
  731. // the file id for path_link_dir_id
  732. .access_denied => {},
  733. .match => return,
  734. .mismatch => {},
  735. }
  736. const exe = try std.fs.path.join(allocator, &.{ path, "zig" });
  737. defer allocator.free(exe);
  738. try enforceNoZig(path_link, exe);
  739. }
  740. }
  741. std.log.err("the path link '{s}' is not in PATH", .{path_link});
  742. return error.AlreadyReported;
  743. }
  744. fn compareDir(dir_id: FileId, other_dir: []const u8) !enum { missing, access_denied, match, mismatch } {
  745. var dir = std.fs.cwd().openDir(other_dir, .{}) catch |err| switch (err) {
  746. error.FileNotFound, error.NotDir, error.BadPathName => return .missing,
  747. error.AccessDenied => return .access_denied,
  748. else => |e| return e,
  749. };
  750. defer dir.close();
  751. return if (dir_id.eql(try FileId.initFromDir(dir, other_dir))) .match else .mismatch;
  752. }
  753. fn enforceNoZig(path_link: []const u8, exe: []const u8) !void {
  754. var file = std.fs.cwd().openFile(exe, .{}) catch |err| switch (err) {
  755. error.FileNotFound, error.IsDir => return,
  756. error.AccessDenied => return, // if there is a Zig it must not be accessible
  757. else => |e| return e,
  758. };
  759. defer file.close();
  760. // todo: on posix systems ignore the file if it is not executable
  761. std.log.err("zig compiler '{s}' is higher priority in PATH than the path-link '{s}'", .{ exe, path_link });
  762. }
  763. const FileId = struct {
  764. dev: if (builtin.os.tag == .windows) u32 else blk: {
  765. const st: std.posix.Stat = undefined;
  766. break :blk @TypeOf(st.dev);
  767. },
  768. ino: if (builtin.os.tag == .windows) u64 else blk: {
  769. const st: std.posix.Stat = undefined;
  770. break :blk @TypeOf(st.ino);
  771. },
  772. pub fn initFromFile(file: std.fs.File, filename_for_error: []const u8) !FileId {
  773. if (builtin.os.tag == .windows) {
  774. var info: win32.BY_HANDLE_FILE_INFORMATION = undefined;
  775. if (0 == win32.GetFileInformationByHandle(file.handle, &info)) {
  776. std.log.err("GetFileInformationByHandle on '{s}' failed, error={}", .{ filename_for_error, std.os.windows.kernel32.GetLastError() });
  777. return error.AlreadyReported;
  778. }
  779. return FileId{
  780. .dev = info.dwVolumeSerialNumber,
  781. .ino = (@as(u64, @intCast(info.nFileIndexHigh)) << 32) | @as(u64, @intCast(info.nFileIndexLow)),
  782. };
  783. }
  784. const st = try std.posix.fstat(file.handle);
  785. return FileId{
  786. .dev = st.dev,
  787. .ino = st.ino,
  788. };
  789. }
  790. pub fn initFromDir(dir: std.fs.Dir, name_for_error: []const u8) !FileId {
  791. if (builtin.os.tag == .windows) {
  792. return initFromFile(std.fs.File{ .handle = dir.fd }, name_for_error);
  793. }
  794. return initFromFile(std.fs.File{ .handle = dir.fd }, name_for_error);
  795. }
  796. pub fn eql(self: FileId, other: FileId) bool {
  797. return self.dev == other.dev and self.ino == other.ino;
  798. }
  799. };
  800. const win32 = struct {
  801. pub const BOOL = i32;
  802. pub const FILETIME = extern struct {
  803. dwLowDateTime: u32,
  804. dwHighDateTime: u32,
  805. };
  806. pub const BY_HANDLE_FILE_INFORMATION = extern struct {
  807. dwFileAttributes: u32,
  808. ftCreationTime: FILETIME,
  809. ftLastAccessTime: FILETIME,
  810. ftLastWriteTime: FILETIME,
  811. dwVolumeSerialNumber: u32,
  812. nFileSizeHigh: u32,
  813. nFileSizeLow: u32,
  814. nNumberOfLinks: u32,
  815. nFileIndexHigh: u32,
  816. nFileIndexLow: u32,
  817. };
  818. pub extern "kernel32" fn GetFileInformationByHandle(
  819. hFile: ?@import("std").os.windows.HANDLE,
  820. lpFileInformation: ?*BY_HANDLE_FILE_INFORMATION,
  821. ) callconv(@import("std").os.windows.WINAPI) BOOL;
  822. };
  823. const win32exelink = struct {
  824. const content = @embedFile("win32exelink");
  825. const exe_offset: usize = if (builtin.os.tag != .windows) 0 else blk: {
  826. @setEvalBranchQuota(content.len * 2);
  827. const marker = "!!!THIS MARKS THE zig_exe_string MEMORY!!#";
  828. const offset = std.mem.indexOf(u8, content, marker) orelse {
  829. @compileError("win32exelink is missing the marker: " ++ marker);
  830. };
  831. if (std.mem.indexOf(u8, content[offset + 1 ..], marker) != null) {
  832. @compileError("win32exelink contains multiple markers (not implemented)");
  833. }
  834. break :blk offset + marker.len;
  835. };
  836. };
  837. fn createExeLink(link_target: []const u8, path_link: []const u8) !void {
  838. if (path_link.len > std.fs.MAX_PATH_BYTES) {
  839. std.debug.print("Error: path_link (size {}) is too large (max {})\n", .{ path_link.len, std.fs.MAX_PATH_BYTES });
  840. return error.AlreadyReported;
  841. }
  842. const file = std.fs.cwd().createFile(path_link, .{}) catch |err| switch (err) {
  843. error.IsDir => {
  844. std.debug.print(
  845. "unable to create the exe link, the path '{s}' is a directory\n",
  846. .{ path_link},
  847. );
  848. std.process.exit(1);
  849. },
  850. else => |e| return e,
  851. };
  852. defer file.close();
  853. try file.writer().writeAll(win32exelink.content[0..win32exelink.exe_offset]);
  854. try file.writer().writeAll(link_target);
  855. try file.writer().writeAll(win32exelink.content[win32exelink.exe_offset + link_target.len ..]);
  856. }
  857. const VersionKind = enum { release, dev };
  858. fn determineVersionKind(version: []const u8) VersionKind {
  859. return if (std.mem.indexOfAny(u8, version, "-+")) |_| .dev else .release;
  860. }
  861. fn getDefaultUrl(allocator: Allocator, compiler_version: []const u8) ![]const u8 {
  862. return switch (determineVersionKind(compiler_version)) {
  863. .dev => try std.fmt.allocPrint(allocator, "https://ziglang.org/builds/zig-" ++ url_platform ++ "-{0s}." ++ archive_ext, .{compiler_version}),
  864. .release => try std.fmt.allocPrint(allocator, "https://ziglang.org/download/{s}/zig-" ++ url_platform ++ "-{0s}." ++ archive_ext, .{compiler_version}),
  865. };
  866. }
  867. fn installCompiler(allocator: Allocator, compiler_dir: []const u8, url: []const u8) !void {
  868. if (try existsAbsolute(compiler_dir)) {
  869. loginfo("compiler '{s}' already installed", .{compiler_dir});
  870. return;
  871. }
  872. const installing_dir = try std.mem.concat(allocator, u8, &[_][]const u8{ compiler_dir, ".installing" });
  873. defer allocator.free(installing_dir);
  874. try loggyDeleteTreeAbsolute(installing_dir);
  875. try loggyMakeDirAbsolute(installing_dir);
  876. const archive_basename = std.fs.path.basename(url);
  877. var archive_root_dir: []const u8 = undefined;
  878. // download and extract archive
  879. {
  880. const archive_absolute = try std.fs.path.join(allocator, &[_][]const u8{ installing_dir, archive_basename });
  881. defer allocator.free(archive_absolute);
  882. loginfo("downloading '{s}' to '{s}'", .{ url, archive_absolute });
  883. switch (blk: {
  884. const file = try std.fs.createFileAbsolute(archive_absolute, .{});
  885. // note: important to close the file before we handle errors below
  886. // since it will delete the parent directory of this file
  887. defer file.close();
  888. break :blk download(allocator, url, file.writer());
  889. }) {
  890. .ok => {},
  891. .err => |err| {
  892. std.log.err("download '{s}' failed: {s}", .{url, err});
  893. // this removes the installing dir if the http request fails so we dont have random directories
  894. try loggyDeleteTreeAbsolute(installing_dir);
  895. return error.AlreadyReported;
  896. },
  897. }
  898. if (std.mem.endsWith(u8, archive_basename, ".tar.xz")) {
  899. archive_root_dir = archive_basename[0 .. archive_basename.len - ".tar.xz".len];
  900. _ = try run(allocator, &[_][]const u8{ "tar", "xf", archive_absolute, "-C", installing_dir });
  901. } else {
  902. var recognized = false;
  903. if (builtin.os.tag == .windows) {
  904. if (std.mem.endsWith(u8, archive_basename, ".zip")) {
  905. recognized = true;
  906. archive_root_dir = archive_basename[0 .. archive_basename.len - ".zip".len];
  907. var installing_dir_opened = try std.fs.openDirAbsolute(installing_dir, .{});
  908. defer installing_dir_opened.close();
  909. loginfo("extracting archive to \"{s}\"", .{installing_dir});
  910. var timer = try std.time.Timer.start();
  911. var archive_file = try std.fs.openFileAbsolute(archive_absolute, .{});
  912. defer archive_file.close();
  913. try zip.extract(installing_dir_opened, archive_file.seekableStream(), .{});
  914. const time = timer.read();
  915. loginfo("extracted archive in {d:.2} s", .{@as(f32, @floatFromInt(time)) / @as(f32, @floatFromInt(std.time.ns_per_s))});
  916. }
  917. }
  918. if (!recognized) {
  919. std.log.err("unknown archive extension '{s}'", .{archive_basename});
  920. return error.UnknownArchiveExtension;
  921. }
  922. }
  923. try loggyDeleteTreeAbsolute(archive_absolute);
  924. }
  925. {
  926. const extracted_dir = try std.fs.path.join(allocator, &[_][]const u8{ installing_dir, archive_root_dir });
  927. defer allocator.free(extracted_dir);
  928. const normalized_dir = try std.fs.path.join(allocator, &[_][]const u8{ installing_dir, "files" });
  929. defer allocator.free(normalized_dir);
  930. try loggyRenameAbsolute(extracted_dir, normalized_dir);
  931. }
  932. // TODO: write date information (so users can sort compilers by date)
  933. // finish installation by renaming the install dir
  934. try loggyRenameAbsolute(installing_dir, compiler_dir);
  935. }
  936. pub fn run(allocator: Allocator, argv: []const []const u8) !std.ChildProcess.Term {
  937. try logRun(allocator, argv);
  938. var proc = std.ChildProcess.init(argv, allocator);
  939. return proc.spawnAndWait();
  940. }
  941. fn logRun(allocator: Allocator, argv: []const []const u8) !void {
  942. var buffer = try allocator.alloc(u8, getCommandStringLength(argv));
  943. defer allocator.free(buffer);
  944. var prefix = false;
  945. var offset: usize = 0;
  946. for (argv) |arg| {
  947. if (prefix) {
  948. buffer[offset] = ' ';
  949. offset += 1;
  950. } else {
  951. prefix = true;
  952. }
  953. @memcpy(buffer[offset .. offset + arg.len], arg);
  954. offset += arg.len;
  955. }
  956. std.debug.assert(offset == buffer.len);
  957. loginfo("[RUN] {s}", .{buffer});
  958. }
  959. pub fn getCommandStringLength(argv: []const []const u8) usize {
  960. var len: usize = 0;
  961. var prefix_length: u8 = 0;
  962. for (argv) |arg| {
  963. len += prefix_length + arg.len;
  964. prefix_length = 1;
  965. }
  966. return len;
  967. }
  968. pub fn getKeepReason(master_points_to_opt: ?[]const u8, default_compiler_opt: ?[]const u8, name: []const u8) ?[]const u8 {
  969. if (default_compiler_opt) |default_comp| {
  970. if (mem.eql(u8, default_comp, name)) {
  971. return "is default compiler";
  972. }
  973. }
  974. if (master_points_to_opt) |master_points_to| {
  975. if (mem.eql(u8, master_points_to, name)) {
  976. return "it is master";
  977. }
  978. }
  979. return null;
  980. }