GitRepoStep.zig 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. const std = @import("std");
  2. const GitRepoStep = @This();
  3. pub const ShaCheck = enum {
  4. none,
  5. warn,
  6. err,
  7. pub fn reportFail(self: ShaCheck, comptime fmt: []const u8, args: anytype) void {
  8. switch (self) {
  9. .none => unreachable,
  10. .warn => std.log.warn(fmt, args),
  11. .err => {
  12. std.log.err(fmt, args);
  13. std.os.exit(0xff);
  14. },
  15. }
  16. }
  17. };
  18. step: std.build.Step,
  19. builder: *std.build.Builder,
  20. url: []const u8,
  21. name: []const u8,
  22. branch: ?[]const u8 = null,
  23. sha: []const u8,
  24. path: []const u8 = null,
  25. sha_check: ShaCheck = .warn,
  26. fetch_enabled: bool,
  27. var cached_default_fetch_option: ?bool = null;
  28. pub fn defaultFetchOption(b: *std.build.Builder) bool {
  29. if (cached_default_fetch_option) |_| { } else {
  30. cached_default_fetch_option = if (b.option(bool, "fetch", "automatically fetch network resources")) |o| o else false;
  31. }
  32. return cached_default_fetch_option.?;
  33. }
  34. pub fn create(b: *std.build.Builder, opt: struct {
  35. url: []const u8,
  36. branch: ?[]const u8 = null,
  37. sha: []const u8,
  38. path: ?[]const u8 = null,
  39. sha_check: ShaCheck = .warn,
  40. fetch_enabled: ?bool = null,
  41. }) *GitRepoStep {
  42. var result = b.allocator.create(GitRepoStep) catch @panic("memory");
  43. const name = std.fs.path.basename(opt.url);
  44. result.* = GitRepoStep {
  45. .step = std.build.Step.init(.custom, "clone a git repository", b.allocator, make),
  46. .builder = b,
  47. .url = opt.url,
  48. .name = name,
  49. .branch = opt.branch,
  50. .sha = opt.sha,
  51. .path = if (opt.path) |p| (b.allocator.dupe(u8, p) catch @panic("memory")) else (
  52. std.fs.path.resolve(b.allocator, &[_][]const u8{
  53. b.build_root,
  54. "dep",
  55. name,
  56. })
  57. ) catch @panic("memory"),
  58. .sha_check = opt.sha_check,
  59. .fetch_enabled = if (opt.fetch_enabled) |fe| fe else defaultFetchOption(b),
  60. };
  61. return result;
  62. }
  63. // TODO: this should be included in std.build, it helps find bugs in build files
  64. fn hasDependency(step: *const std.build.Step, dep_candidate: *const std.build.Step) bool {
  65. for (step.dependencies.items) |dep| {
  66. // TODO: should probably use step.loop_flag to prevent infinite recursion
  67. // when a circular reference is encountered, or maybe keep track of
  68. // the steps encounterd with a hash set
  69. if (dep == dep_candidate or hasDependency(dep, dep_candidate))
  70. return true;
  71. }
  72. return false;
  73. }
  74. fn make(step: *std.build.Step) !void {
  75. const self = @fieldParentPtr(GitRepoStep, "step", step);
  76. std.fs.accessAbsolute(self.path, std.fs.File.OpenFlags { .read = true }) catch {
  77. const branch_args = if (self.branch) |b| &[2][]const u8 {" -b ", b} else &[2][]const u8 {"", ""};
  78. if (!self.fetch_enabled) {
  79. std.debug.print("Error: git repository '{s}' does not exist\n", .{self.path});
  80. std.debug.print(" Use -Dfetch to download it automatically, or run the following to clone it:\n", .{});
  81. std.debug.print(" git clone {s}{s}{s} {s} && git -C {3s} checkout {s} -b for_ziget\n",
  82. .{self.url, branch_args[0], branch_args[1], self.path, self.sha});
  83. std.os.exit(1);
  84. }
  85. {
  86. var args = std.ArrayList([]const u8).init(self.builder.allocator);
  87. defer args.deinit();
  88. try args.append("git");
  89. try args.append("clone");
  90. try args.append(self.url);
  91. // TODO: clone it to a temporary location in case of failure
  92. // also, remove that temporary location before running
  93. try args.append(self.path);
  94. if (self.branch) |branch| {
  95. try args.append("-b");
  96. try args.append(branch);
  97. }
  98. try run(self.builder, args.items);
  99. }
  100. try run(self.builder, &[_][]const u8 {
  101. "git",
  102. "-C", self.path,
  103. "checkout",
  104. self.sha,
  105. "-b",
  106. "fordep",
  107. });
  108. };
  109. try self.checkSha();
  110. }
  111. fn checkSha(self: GitRepoStep) !void {
  112. if (self.sha_check == .none)
  113. return;
  114. const result: union(enum) { failed: anyerror, output: []const u8 } = blk: {
  115. const result = std.ChildProcess.exec(.{
  116. .allocator = self.builder.allocator,
  117. .argv = &[_][]const u8 {
  118. "git",
  119. "-C", self.path,
  120. "rev-parse",
  121. "HEAD",
  122. },
  123. .cwd = self.builder.build_root,
  124. .env_map = self.builder.env_map,
  125. }) catch |e| break :blk .{ .failed = e };
  126. try std.io.getStdErr().writer().writeAll(result.stderr);
  127. switch (result.term) {
  128. .Exited => |code| {
  129. if (code == 0) break :blk .{ .output = result.stdout };
  130. break :blk .{ .failed = error.GitProcessNonZeroExit };
  131. },
  132. .Signal => break :blk .{ .failed = error.GitProcessFailedWithSignal },
  133. .Stopped => break :blk .{ .failed = error.GitProcessWasStopped },
  134. .Unknown => break :blk .{ .failed = error.GitProcessFailed },
  135. }
  136. };
  137. switch (result) {
  138. .failed => |err| {
  139. return self.sha_check.reportFail("failed to retreive sha for repository '{s}': {s}", .{self.name, @errorName(err)});
  140. },
  141. .output => |output| {
  142. if (!std.mem.eql(u8, std.mem.trimRight(u8, output, "\n\r"), self.sha)) {
  143. return self.sha_check.reportFail("repository '{s}' sha does not match\nexpected: {s}\nactual : {s}\n", .{self.name, self.sha, output});
  144. }
  145. },
  146. }
  147. }
  148. fn run(builder: *std.build.Builder, argv: []const []const u8) !void {
  149. {
  150. var msg = std.ArrayList(u8).init(builder.allocator);
  151. defer msg.deinit();
  152. const writer = msg.writer();
  153. var prefix: []const u8 = "";
  154. for (argv) |arg| {
  155. try writer.print("{s}\"{s}\"", .{prefix, arg});
  156. prefix = " ";
  157. }
  158. std.log.info("[RUN] {s}", .{msg.items});
  159. }
  160. const child = try std.ChildProcess.init(argv, builder.allocator);
  161. defer child.deinit();
  162. child.stdin_behavior = .Ignore;
  163. child.stdout_behavior = .Inherit;
  164. child.stderr_behavior = .Inherit;
  165. child.cwd = builder.build_root;
  166. child.env_map = builder.env_map;
  167. try child.spawn();
  168. const result = try child.wait();
  169. switch (result) {
  170. .Exited => |code| if (code != 0) {
  171. std.log.err("git clone failed with exit code {}", .{code});
  172. std.os.exit(0xff);
  173. },
  174. else => {
  175. std.log.err("git clone failed with: {}", .{result});
  176. std.os.exit(0xff);
  177. },
  178. }
  179. }
  180. // Get's the repository path and also verifies that the step requesting the path
  181. // is dependent on this step.
  182. pub fn getPath(self: *const GitRepoStep, who_wants_to_know: *const std.build.Step) []const u8 {
  183. if (!hasDependency(who_wants_to_know, &self.step))
  184. @panic("a step called GitRepoStep.getPath but has not added it as a dependency");
  185. return self.path;
  186. }