zigup.zig 40 KB

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