build.zig 15 KB

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