build.zig 16 KB

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