zigup.zig 43 KB

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