build.zig 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. const std = @import("std");
  2. const builtin = @import("builtin");
  3. const Builder = std.build.Builder;
  4. const Pkg = std.build.Pkg;
  5. pub fn build(b: *Builder) !void {
  6. const optional_ssl_backend = getSslBackend(b);
  7. const target = b.standardTargetOptions(.{});
  8. const mode = b.standardReleaseOptions();
  9. const exe = b.addExecutable("ziget", "ziget-cmdline.zig");
  10. exe.setTarget(target);
  11. exe.single_threaded = true;
  12. exe.setBuildMode(mode);
  13. exe.addPackage(
  14. if (optional_ssl_backend) |ssl_backend| try addSslBackend(exe, ssl_backend, ".")
  15. else Pkg { .name = "ssl", .path = .{ .path = "nossl/ssl.zig" } }
  16. );
  17. exe.install();
  18. const run_cmd = exe.run();
  19. run_cmd.step.dependOn(b.getInstallStep());
  20. const run_step = b.step("run", "Run the app");
  21. run_step.dependOn(&run_cmd.step);
  22. addTests(b, target, mode);
  23. }
  24. fn addTests(b: *Builder, target: std.zig.CrossTarget, mode: std.builtin.Mode) void {
  25. const test_exe = b.addExecutable("test", "test.zig");
  26. test_exe.setTarget(target);
  27. test_exe.setBuildMode(mode);
  28. const test_step = b.step("test", "Run all the 'Enabled' tests");
  29. inline for (ssl_backends) |field| {
  30. const enum_value = @field(SslBackend, field.name);
  31. const enabled_by_default =
  32. if (enum_value == .wolfssl) false
  33. else if (enum_value == .schannel and builtin.os.tag != .windows) false
  34. else true;
  35. addTest(test_step, test_exe, field.name, enabled_by_default);
  36. }
  37. addTest(test_step, test_exe, "nossl", true);
  38. }
  39. fn addTest(test_step: *std.build.Step, test_exe: *std.build.LibExeObjStep, comptime backend_name: []const u8, comptime enabled_by_default: bool) void {
  40. const b = test_exe.builder;
  41. const run_cmd = test_exe.run();
  42. run_cmd.addArg(backend_name);
  43. run_cmd.step.dependOn(b.getInstallStep());
  44. const enabled_prefix = if (enabled_by_default) "Enabled " else "Disabled";
  45. const test_backend_step = b.step("test-" ++ backend_name,
  46. enabled_prefix ++ ": test ziget with the '" ++ backend_name ++ "' ssl backend");
  47. test_backend_step.dependOn(&run_cmd.step);
  48. if (enabled_by_default) {
  49. test_step.dependOn(&run_cmd.step);
  50. }
  51. }
  52. pub fn unwrapOptionalBool(optionalBool: ?bool) bool {
  53. if (optionalBool) |b| return b;
  54. return false;
  55. }
  56. pub const SslBackend = enum {
  57. openssl,
  58. opensslstatic,
  59. wolfssl,
  60. iguana,
  61. schannel,
  62. };
  63. pub const ssl_backends = @typeInfo(SslBackend).Enum.fields;
  64. pub fn getSslBackend(b: *Builder) ?SslBackend {
  65. var backend: ?SslBackend = null;
  66. var backend_infos : [ssl_backends.len]struct {
  67. enabled: bool,
  68. name: []const u8,
  69. } = undefined;
  70. var backend_enabled_count: u32 = 0;
  71. inline for (ssl_backends) |field, i| {
  72. const enabled = unwrapOptionalBool(b.option(bool, field.name, "enable ssl backend: " ++ field.name));
  73. if (enabled) {
  74. backend = @field(SslBackend, field.name);
  75. backend_enabled_count += 1;
  76. }
  77. backend_infos[i] = .{
  78. .enabled = enabled,
  79. .name = field.name,
  80. };
  81. }
  82. if (backend_enabled_count > 1) {
  83. std.log.err("only one ssl backend may be enabled, can't provide these options at the same time:", .{});
  84. for (backend_infos) |info| {
  85. if (info.enabled) {
  86. std.log.err(" -D{s}", .{info.name});
  87. }
  88. }
  89. std.os.exit(1);
  90. }
  91. return backend;
  92. }
  93. //
  94. // NOTE: the ziget_repo argument is here so this function can be used by other projects, not just this repo
  95. //
  96. pub fn addSslBackend(step: *std.build.LibExeObjStep, backend: SslBackend, ziget_repo: []const u8) !Pkg {
  97. const b = step.builder;
  98. switch (backend) {
  99. .openssl => {
  100. step.linkSystemLibrary("c");
  101. if (builtin.os.tag == .windows) {
  102. step.linkSystemLibrary("libcrypto");
  103. step.linkSystemLibrary("libssl");
  104. try setupOpensslWindows(step);
  105. } else {
  106. step.linkSystemLibrary("crypto");
  107. step.linkSystemLibrary("ssl");
  108. }
  109. return Pkg {
  110. .name = "ssl",
  111. .path = .{ .path = try std.fs.path.join(b.allocator, &[_][]const u8 { ziget_repo, "openssl/ssl.zig" }) },
  112. };
  113. },
  114. .opensslstatic => {
  115. const openssl_repo = try (GitRepo {
  116. .url = "https://github.com/openssl/openssl",
  117. .branch = "OpenSSL_1_1_1j",
  118. .sha = "52c587d60be67c337364b830dd3fdc15404a2f04",
  119. }).resolve(b.allocator);
  120. // TODO: should we implement something to cache the configuration?
  121. // can the configure output be in a different directory?
  122. {
  123. const configure_openssl = std.build.RunStep.create(b, "configure openssl");
  124. configure_openssl.cwd = openssl_repo;
  125. configure_openssl.addArgs(&[_][]const u8 {
  126. "./config",
  127. // just a temporary path for now
  128. //"--openssl",
  129. //"/tmp/ziget-openssl-static-dir1",
  130. "-static",
  131. // just disable everything for now
  132. "no-threads",
  133. "no-shared",
  134. "no-asm",
  135. "no-sse2",
  136. "no-aria",
  137. "no-bf",
  138. "no-camellia",
  139. "no-cast",
  140. "no-des",
  141. "no-dh",
  142. "no-dsa",
  143. "no-ec",
  144. "no-idea",
  145. "no-md2",
  146. "no-mdc2",
  147. "no-rc2",
  148. "no-rc4",
  149. "no-rc5",
  150. "no-seed",
  151. "no-sm2",
  152. "no-sm3",
  153. "no-sm4",
  154. });
  155. configure_openssl.stdout_action = .{
  156. .expect_matches = &[_][]const u8 { "OpenSSL has been successfully configured" },
  157. };
  158. const make_openssl = std.build.RunStep.create(b, "configure openssl");
  159. make_openssl.cwd = openssl_repo;
  160. make_openssl.addArgs(&[_][]const u8 {
  161. "make",
  162. "include/openssl/opensslconf.h",
  163. "include/crypto/bn_conf.h",
  164. "include/crypto/dso_conf.h",
  165. });
  166. make_openssl.step.dependOn(&configure_openssl.step);
  167. step.step.dependOn(&make_openssl.step);
  168. }
  169. step.addIncludeDir(openssl_repo);
  170. step.addIncludeDir(try std.fs.path.join(b.allocator, &[_][]const u8 { openssl_repo, "include" }));
  171. step.addIncludeDir(try std.fs.path.join(b.allocator, &[_][]const u8 { openssl_repo, "crypto", "modes" }));
  172. const cflags = &[_][]const u8 {
  173. "-Wall",
  174. // TODO: is this the right way to do this? is it a config option?
  175. "-DOPENSSL_NO_ENGINE",
  176. // TODO: --openssldir doesn't seem to be setting this?
  177. "-DOPENSSLDIR=\"/tmp/ziget-openssl-static-dir2\"",
  178. };
  179. {
  180. const sources = @embedFile("openssl/sources");
  181. var source_lines = std.mem.split(u8, sources, "\n");
  182. while (source_lines.next()) |src| {
  183. if (src.len == 0 or src[0] == '#') continue;
  184. step.addCSourceFile(try std.fs.path.join(b.allocator, &[_][]const u8 { openssl_repo, src }), cflags);
  185. }
  186. }
  187. step.linkLibC();
  188. return Pkg {
  189. .name = "ssl",
  190. .path = .{ .path = try std.fs.path.join(b.allocator, &[_][]const u8 { ziget_repo, "openssl/ssl.zig" }) },
  191. };
  192. },
  193. .wolfssl => {
  194. std.log.err("-Dwolfssl is not implemented", .{});
  195. std.os.exit(1);
  196. },
  197. .iguana => {
  198. const iguana_index_file = try (GitRepo {
  199. .url = "https://github.com/marler8997/iguanaTLS",
  200. .branch = null,
  201. .sha = @embedFile("iguanasha"),
  202. }).resolveOneFile(b.allocator, "src" ++ std.fs.path.sep_str ++ "main.zig");
  203. var p = Pkg {
  204. .name = "ssl",
  205. .path = .{ .path = try std.fs.path.join(b.allocator, &[_][]const u8 { ziget_repo, "iguana", "ssl.zig" }) },
  206. .dependencies = &[_]Pkg {
  207. .{ .name = "iguana", .path = .{ .path = iguana_index_file } },
  208. },
  209. };
  210. // NOTE: I don't know why I need to call dupePkg, I think this is a bug
  211. return b.dupePkg(p);
  212. },
  213. .schannel => {
  214. {
  215. // NOTE: for now I'm using msspi from https://github.com/deemru/msspi
  216. // I'll probably port this to Zig at some point
  217. // Once I do remove this build config
  218. // NOTE: I tested using this commit: 7338760a4a2c6fb80c47b24a2abba32d5fc40635 tagged at version 0.1.42
  219. const msspi_repo = try (GitRepo {
  220. .url = "https://github.com/deemru/msspi",
  221. .branch = "0.1.42",
  222. .sha = "7338760a4a2c6fb80c47b24a2abba32d5fc40635"
  223. }).resolve(b.allocator);
  224. const msspi_src_dir = try std.fs.path.join(b.allocator, &[_][]const u8 { msspi_repo, "src" });
  225. const msspi_main_cpp = try std.fs.path.join(b.allocator, &[_][]const u8 { msspi_src_dir, "msspi.cpp" });
  226. const msspi_third_party_include = try std.fs.path.join(b.allocator, &[_][]const u8 { msspi_repo, "third_party", "cprocsp", "include" });
  227. step.addCSourceFile(msspi_main_cpp, &[_][]const u8 { });
  228. step.addIncludeDir(msspi_src_dir);
  229. step.addIncludeDir(msspi_third_party_include);
  230. step.linkLibC();
  231. step.linkSystemLibrary("ws2_32");
  232. step.linkSystemLibrary("crypt32");
  233. step.linkSystemLibrary("advapi32");
  234. }
  235. // TODO: this will be needed if/when msspi is ported to Zig
  236. //const zigwin32_index_file = try getGitRepoFile(b.allocator,
  237. // "https://github.com/marlersoft/zigwin32",
  238. // "src" ++ std.fs.path.sep_str ++ "win32.zig");
  239. return Pkg {
  240. .name = "ssl",
  241. .path = .{ .path = try std.fs.path.join(b.allocator, &[_][]const u8 { ziget_repo, "schannel", "ssl.zig" }) },
  242. //.dependencies = &[_]Pkg {
  243. // .{ .name = "win32", .path = .{ .path = zigwin32_index_file } },
  244. //},
  245. };
  246. }
  247. }
  248. }
  249. pub fn setupOpensslWindows(step: *std.build.LibExeObjStep) !void {
  250. const b = step.builder;
  251. const openssl_path = b.option([]const u8, "openssl-path", "path to openssl (for Windows)") orelse {
  252. std.debug.print("Error: -Dopenssl on windows requires -Dopenssl-path=DIR to be specified\n", .{});
  253. std.os.exit(1);
  254. };
  255. // NOTE: right now these files are hardcoded to the files expected when installing SSL via
  256. // this web page: https://slproweb.com/products/Win32OpenSSL.html and installed using
  257. // this exe installer: https://slproweb.com/download/Win64OpenSSL-1_1_1g.exe
  258. step.addIncludeDir(try std.fs.path.join(b.allocator, &[_][]const u8 {openssl_path, "include"}));
  259. step.addLibPath(try std.fs.path.join(b.allocator, &[_][]const u8 {openssl_path, "lib"}));
  260. // install dlls to the same directory as executable
  261. for ([_][]const u8 {"libcrypto-1_1-x64.dll", "libssl-1_1-x64.dll"}) |dll| {
  262. step.step.dependOn(
  263. &b.addInstallFileWithDir(
  264. .{ .path = try std.fs.path.join(b.allocator, &[_][]const u8 {openssl_path, dll}) },
  265. .bin,
  266. dll,
  267. ).step
  268. );
  269. }
  270. }
  271. pub const GitRepo = struct {
  272. url: []const u8,
  273. branch: ?[]const u8,
  274. sha: []const u8,
  275. path: ?[]const u8 = null,
  276. pub fn defaultReposDir(allocator: *std.mem.Allocator) ![]const u8 {
  277. const cwd = try std.process.getCwdAlloc(allocator);
  278. defer allocator.free(cwd);
  279. return try std.fs.path.join(allocator, &[_][]const u8 { cwd, "dep" });
  280. }
  281. pub fn resolve(self: GitRepo, allocator: *std.mem.Allocator) ![]const u8 {
  282. var optional_repos_dir_to_clean: ?[]const u8 = null;
  283. defer {
  284. if (optional_repos_dir_to_clean) |p| {
  285. allocator.free(p);
  286. }
  287. }
  288. const path = if (self.path) |p| try allocator.dupe(u8, p) else blk: {
  289. const repos_dir = try defaultReposDir(allocator);
  290. optional_repos_dir_to_clean = repos_dir;
  291. break :blk try std.fs.path.join(allocator, &[_][]const u8{ repos_dir, std.fs.path.basename(self.url) });
  292. };
  293. errdefer allocator.free(path);
  294. std.fs.accessAbsolute(path, std.fs.File.OpenFlags { .read = true }) catch {
  295. std.debug.print("Error: repository '{s}' does not exist\n", .{path});
  296. std.debug.print(" Run the following to clone it:\n", .{});
  297. const branch_args = if (self.branch) |b| &[2][]const u8 {" -b ", b} else &[2][]const u8 {"", ""};
  298. std.debug.print(" git clone {s}{s}{s} {s} && git -C {3s} checkout {s} -b for_ziget\n",
  299. .{self.url, branch_args[0], branch_args[1], path, self.sha});
  300. std.os.exit(1);
  301. };
  302. // TODO: check if the SHA matches an print a message and/or warning if it is different
  303. return path;
  304. }
  305. pub fn resolveOneFile(self: GitRepo, allocator: *std.mem.Allocator, index_sub_path: []const u8) ![]const u8 {
  306. const repo_path = try self.resolve(allocator);
  307. defer allocator.free(repo_path);
  308. return try std.fs.path.join(allocator, &[_][]const u8 { repo_path, index_sub_path });
  309. }
  310. };