@pkmn/engine 0.1.0-dev.5570b5c5 → 0.1.0-dev.587596a6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +29 -17
  3. package/build/pkg/addon/browser.d.ts +3 -0
  4. package/build/pkg/addon/browser.js +47 -0
  5. package/build/pkg/addon/browser.js.map +1 -0
  6. package/build/pkg/addon/node.d.ts +3 -0
  7. package/build/pkg/addon/node.js +110 -0
  8. package/build/pkg/addon/node.js.map +1 -0
  9. package/build/pkg/addon/wasm.d.ts +2 -0
  10. package/build/pkg/addon/wasm.js +53 -0
  11. package/build/pkg/addon/wasm.js.map +1 -0
  12. package/build/pkg/addon.d.ts +26 -4
  13. package/build/pkg/addon.js +34 -65
  14. package/build/pkg/addon.js.map +1 -1
  15. package/build/pkg/data/layout.json +15 -2
  16. package/build/pkg/data/protocol.json +23 -9
  17. package/build/pkg/data.d.ts +23 -1
  18. package/build/pkg/data.js.map +1 -1
  19. package/build/pkg/gen1.d.ts +15 -18
  20. package/build/pkg/gen1.js +28 -19
  21. package/build/pkg/gen1.js.map +1 -1
  22. package/build/pkg/index.d.ts +40 -90
  23. package/build/pkg/index.js +23 -70
  24. package/build/pkg/index.js.map +1 -1
  25. package/build/pkg/protocol.d.ts +20 -1
  26. package/build/pkg/protocol.js +105 -103
  27. package/build/pkg/protocol.js.map +1 -1
  28. package/build/tools/debug.js +68 -74
  29. package/build/tools/debug.js.map +1 -1
  30. package/build/tools/display/debug.jsx +152 -0
  31. package/build/tools/display/debug.jsx.map +1 -0
  32. package/build/tools/display/index.js +152 -0
  33. package/build/tools/display/index.js.map +1 -0
  34. package/build/tools/display/ui.jsx +359 -0
  35. package/build/tools/display/ui.jsx.map +1 -0
  36. package/build/tools/display/util.js +33 -0
  37. package/build/tools/display/util.js.map +1 -0
  38. package/build.zig +237 -98
  39. package/package.json +59 -138
  40. package/src/bin/install-pkmn-engine +25 -7
  41. package/src/bin/pkmn-debug +1 -1
  42. package/src/lib/bindings/c.zig +200 -0
  43. package/src/lib/bindings/node.zig +117 -0
  44. package/src/lib/bindings/wasm.zig +50 -0
  45. package/src/lib/common/array.zig +70 -0
  46. package/src/lib/common/data.zig +8 -8
  47. package/src/lib/common/debug.zig +32 -11
  48. package/src/lib/common/js.zig +3 -7
  49. package/src/lib/common/optional.zig +11 -17
  50. package/src/lib/common/options.zig +6 -0
  51. package/src/lib/common/protocol.zig +148 -134
  52. package/src/lib/common/rational.zig +338 -255
  53. package/src/lib/common/rng.zig +5 -7
  54. package/src/lib/common/util.zig +33 -1
  55. package/src/lib/gen1/calc.zig +701 -171
  56. package/src/lib/gen1/chance.zig +407 -298
  57. package/src/lib/gen1/data/moves.zig +2 -4
  58. package/src/lib/gen1/data/species.zig +1 -3
  59. package/src/lib/gen1/data/types.zig +3 -4
  60. package/src/lib/gen1/data.zig +43 -46
  61. package/src/lib/gen1/helpers.zig +91 -549
  62. package/src/lib/gen1/mechanics.zig +355 -258
  63. package/src/lib/gen2/calc.zig +35 -28
  64. package/src/lib/gen2/chance.zig +91 -510
  65. package/src/lib/gen2/data/items.zig +1 -3
  66. package/src/lib/gen2/data/moves.zig +2 -4
  67. package/src/lib/gen2/data/species.zig +1 -3
  68. package/src/lib/gen2/data/types.zig +7 -5
  69. package/src/lib/gen2/data.zig +33 -35
  70. package/src/lib/gen2/generated.zig +18 -23
  71. package/src/lib/gen2/helpers.zig +45 -26
  72. package/src/lib/gen2/mechanics.zig +97 -124
  73. package/src/lib/napi.h +2 -0
  74. package/src/lib/node.zig +4 -121
  75. package/src/lib/pkmn.zig +106 -33
  76. package/src/lib/wasm.zig +9 -9
  77. package/src/tools/display/dom.ts +93 -0
  78. package/src/tools/display/ui.css +354 -0
  79. package/build/test/display.js +0 -400
  80. package/build/test/display.js.map +0 -1
  81. package/src/test/display/pkmn.html.tmpl +0 -368
package/build.zig CHANGED
@@ -1,5 +1,9 @@
1
- const std = @import("std");
2
1
  const builtin = @import("builtin");
2
+ const std = @import("std");
3
+
4
+ fn ArrayList(comptime T: type) type {
5
+ return std.array_list.Aligned(T, null);
6
+ }
3
7
 
4
8
  pub const Options = struct {
5
9
  showdown: ?bool = null,
@@ -15,10 +19,12 @@ pub fn module(b: *std.Build, options: Options) *std.Build.Module {
15
19
  build_options.addOption(?bool, "log", options.log);
16
20
  build_options.addOption(?bool, "chance", options.chance);
17
21
  build_options.addOption(?bool, "calc", options.calc);
18
- return b.createModule(.{
19
- .root_source_file = .{ .cwd_relative = dirname ++ "/src/lib/pkmn.zig" },
20
- .imports = &.{.{ .name = "build_options", .module = build_options.createModule() }},
21
- });
22
+
23
+ const root_source_file: std.Build.LazyPath =
24
+ .{ .cwd_relative = dirname ++ "/src/lib/pkmn.zig" };
25
+ const imports: []const std.Build.Module.Import =
26
+ &.{.{ .name = "build_options", .module = build_options.createModule() }};
27
+ return b.createModule(.{ .root_source_file = root_source_file, .imports = imports });
22
28
  }
23
29
 
24
30
  pub fn build(b: *std.Build) !void {
@@ -28,6 +34,10 @@ pub fn build(b: *std.Build) !void {
28
34
  const node_headers = b.option([]const u8, "node-headers", "Path to node-headers");
29
35
  const node_import_lib =
30
36
  b.option([]const u8, "node-import-library", "Path to node import library (Windows)");
37
+ const demo = if (try exists(b, "src/tools/demo.zig"))
38
+ b.option(bool, "demo", "Build the demo library") orelse false
39
+ else
40
+ false;
31
41
  const wasm = b.option(bool, "wasm", "Build a WASM library") orelse false;
32
42
  const wasm_stack_size =
33
43
  b.option(u64, "wasm-stack-size", "The size of WASM stack") orelse std.wasm.page_size;
@@ -52,6 +62,7 @@ pub fn build(b: *std.Build) !void {
52
62
  const log = b.option(bool, "log", "Enable protocol message logging") orelse false;
53
63
  const chance = b.option(bool, "chance", "Enable update probability tracking") orelse false;
54
64
  const calc = b.option(bool, "calc", "Enable damage calculator support") orelse false;
65
+ const extra = b.option([]const []const u8, "option", "Enable extra option");
55
66
 
56
67
  const options = b.addOptions();
57
68
  options.addOption(?bool, "showdown", showdown);
@@ -59,26 +70,61 @@ pub fn build(b: *std.Build) !void {
59
70
  options.addOption(?bool, "chance", chance);
60
71
  options.addOption(?bool, "calc", calc);
61
72
 
73
+ if (extra) |opts| {
74
+ for (opts) |opt| {
75
+ if (std.mem.indexOfScalar(u8, opt, '=')) |i| {
76
+ options.addOption(?bool, opt[0..i], if (std.mem.eql(u8, opt[i + 1 ..], "true"))
77
+ true
78
+ else if (std.mem.eql(u8, opt[i + 1 ..], "false"))
79
+ false
80
+ else {
81
+ std.log.err("Invalid option: {s}\n", .{opt});
82
+ return error.InvalidOption;
83
+ });
84
+ } else {
85
+ options.addOption(?bool, opt, true);
86
+ }
87
+ }
88
+ }
89
+
62
90
  const name = if (showdown) "pkmn-showdown" else "pkmn";
63
91
 
92
+ const pkmn: *std.Build.Module = b.addModule("pkmn", .{
93
+ .root_source_file = b.path("src/lib/pkmn.zig"),
94
+ .optimize = optimize,
95
+ .target = target,
96
+ .imports = &.{.{ .name = "build_options", .module = options.createModule() }},
97
+ });
98
+
64
99
  var c = false;
65
100
  if (node_headers) |headers| {
101
+ const translate_c = b.addTranslateC(.{
102
+ .root_source_file = b.path("src/lib/napi.h"),
103
+ .target = target,
104
+ .optimize = optimize,
105
+ });
106
+ translate_c.addSystemIncludePath(b.path(headers));
66
107
  const addon = b.fmt("{s}.node", .{name});
67
- const lib = b.addSharedLibrary(.{
108
+ const path = b.path("src/lib/node.zig");
109
+ const lib = b.addLibrary(.{
110
+ .linkage = .dynamic,
68
111
  .name = addon,
69
- .root_source_file = b.path("src/lib/node.zig"),
70
- .optimize = optimize,
71
- .target = target,
72
- .strip = strip,
73
- .pic = pic,
112
+ .root_module = b.createModule(.{
113
+ .root_source_file = path,
114
+ .optimize = optimize,
115
+ .target = target,
116
+ .strip = strip,
117
+ .pic = pic,
118
+ }),
74
119
  });
75
120
  lib.root_module.addOptions("build_options", options);
76
- lib.addSystemIncludePath(b.path(headers));
77
- lib.linkLibC();
121
+ lib.root_module.addImport("napi", translate_c.createModule());
122
+ lib.root_module.link_libc = true;
78
123
  if (node_import_lib) |il| {
79
- lib.addObjectFile(b.path(il));
124
+ lib.root_module.addObjectFile(b.path(il));
80
125
  } else if (target.result.os.tag == .windows) {
81
- try std.io.getStdErr().writeAll("Must provide --node-import-library path on Windows\n");
126
+ var err = std.Io.File.stderr().writer(b.graph.io, &.{});
127
+ try err.interface.writeAll("Must provide --node-import-library path on Windows\n");
82
128
  std.process.exit(1);
83
129
  }
84
130
  lib.linker_allow_shlib_undefined = true;
@@ -93,67 +139,46 @@ pub fn build(b: *std.Build) !void {
93
139
  // rename the file ourself in install-pkmn-engine
94
140
  b.installArtifact(lib);
95
141
  } else if (wasm) {
96
- const exe = b.addExecutable(.{
97
- .name = name,
98
- .root_source_file = b.path("src/lib/wasm.zig"),
99
- .optimize = switch (optimize) {
100
- .ReleaseFast, .ReleaseSafe => .ReleaseSmall,
101
- else => optimize,
102
- },
103
- .target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
104
- .strip = strip,
105
- .pic = pic,
106
- });
107
- exe.root_module.addOptions("build_options", options);
108
- exe.root_module.export_symbol_names = &[_][]const u8{
109
- "SHOWDOWN",
110
- "LOG",
111
- "CHANCE",
112
- "CALC",
113
- "GEN1_CHOICES_SIZE",
114
- "GEN1_LOGS_SIZE",
115
- };
116
- exe.entry = .disabled;
117
- exe.stack_size = wasm_stack_size;
118
- const opt = b.findProgram(&.{"wasm-opt"}, &.{"./node_modules/.bin"}) catch null;
119
- if (optimize != .Debug and opt != null) {
120
- const out = b.fmt("build/lib/{s}.wasm", .{name});
121
- const sh = b.addSystemCommand(&.{ opt.?, "-O4" });
122
- sh.addArtifactArg(exe);
123
- sh.addArg("-o");
124
- sh.addFileArg(b.path(out));
125
- b.getInstallStep().dependOn(&sh.step);
126
- } else {
127
- b.getInstallStep().dependOn(&b.addInstallArtifact(exe, .{
128
- .dest_dir = .{ .override = std.Build.InstallDir{ .lib = {} } },
129
- }).step);
130
- }
142
+ const path = "src/lib/wasm.zig";
143
+ try buildWasm(b, name, path, optimize, strip, pic, wasm_stack_size, null, options);
144
+ } else if (demo) {
145
+ const path = "src/tools/demo.zig";
146
+ const n = if (showdown) "demo-showdown" else "demo";
147
+ const mod = pkmn;
148
+ try buildWasm(b, n, path, optimize, strip, pic, wasm_stack_size, mod, options);
131
149
  } else if (dynamic) {
132
- const lib = b.addSharedLibrary(.{
150
+ const path = b.path("src/lib/c.zig");
151
+ const lib = b.addLibrary(.{
152
+ .linkage = .dynamic,
133
153
  .name = name,
134
- .root_source_file = b.path("src/lib/c.zig"),
135
- .version = try std.SemanticVersion.parse(version),
136
- .optimize = optimize,
137
- .target = target,
138
- .strip = strip,
139
- .pic = pic,
154
+ .root_module = b.createModule(.{
155
+ .root_source_file = path,
156
+ .optimize = optimize,
157
+ .target = target,
158
+ .strip = strip,
159
+ .pic = pic,
160
+ }),
140
161
  });
141
162
  lib.root_module.addOptions("build_options", options);
142
- lib.addIncludePath(b.path("src/include"));
163
+ lib.root_module.addIncludePath(b.path("src/include"));
143
164
  maybeStrip(b, lib, b.getInstallStep(), strip, cmd);
144
165
  b.installArtifact(lib);
145
166
  c = true;
146
167
  } else {
147
- const lib = b.addStaticLibrary(.{
168
+ const path = b.path("src/lib/c.zig");
169
+ const lib = b.addLibrary(.{
170
+ .linkage = .static,
148
171
  .name = name,
149
- .root_source_file = b.path("src/lib/c.zig"),
150
- .optimize = optimize,
151
- .target = target,
152
- .strip = strip,
153
- .pic = pic,
172
+ .root_module = b.createModule(.{
173
+ .root_source_file = path,
174
+ .optimize = optimize,
175
+ .target = target,
176
+ .strip = strip,
177
+ .pic = pic,
178
+ }),
154
179
  });
155
180
  lib.root_module.addOptions("build_options", options);
156
- lib.addIncludePath(b.path("src/include"));
181
+ lib.root_module.addIncludePath(b.path("src/include"));
157
182
  lib.bundle_compiler_rt = true;
158
183
  maybeStrip(b, lib, b.getInstallStep(), strip, cmd);
159
184
  if (emit_asm) {
@@ -175,24 +200,25 @@ pub fn build(b: *std.Build) !void {
175
200
  }
176
201
 
177
202
  if (c) {
178
- const header = b.addInstallFileWithDir(
179
- b.path("src/include/pkmn.h"),
180
- .header,
181
- "pkmn.h",
182
- );
203
+ const path = b.path("src/include/pkmn.h");
204
+ const header = b.addInstallFileWithDir(path, .header, "pkmn.h");
183
205
  b.getInstallStep().dependOn(&header.step);
184
206
 
185
207
  const pc = b.fmt("lib{s}.pc", .{name});
186
- const file = try std.fs.path.relative(
208
+ const cwd = try std.process.getCwdAlloc(b.allocator);
209
+ const file = try std.Io.Dir.path.relative(
187
210
  b.allocator,
188
211
  try std.process.getCwdAlloc(b.allocator),
212
+ &b.graph.environ_map,
213
+ cwd,
189
214
  try b.cache_root.join(b.allocator, &.{pc}),
190
215
  );
191
- const pkgconfig_file = try std.fs.cwd().createFile(file, .{});
216
+ const pkgconfig_file = try std.Io.Dir.cwd().createFile(b.graph.io, file, .{});
217
+ defer pkgconfig_file.close(b.graph.io);
192
218
 
193
219
  const dirname = comptime std.fs.path.dirname(@src().file) orelse ".";
194
- const writer = pkgconfig_file.writer();
195
- try writer.print(
220
+ var writer = pkgconfig_file.writer(b.graph.io, &.{});
221
+ try writer.interface.print(
196
222
  \\prefix={0s}/{1s}
197
223
  \\includedir=${{prefix}}/include
198
224
  \\libdir=${{prefix}}/lib
@@ -204,12 +230,11 @@ pub fn build(b: *std.Build) !void {
204
230
  \\Cflags: -I${{includedir}}
205
231
  \\Libs: -L${{libdir}} -l{2s}
206
232
  , .{ dirname, b.install_path, name, repository.next().?, description, version });
207
- defer pkgconfig_file.close();
208
233
 
209
234
  b.installFile(file, b.fmt("share/pkgconfig/{s}", .{pc}));
210
235
  }
211
236
 
212
- const config = .{
237
+ const config: Config = .{
213
238
  .target = target,
214
239
  .optimize = optimize,
215
240
  .pic = pic,
@@ -220,7 +245,7 @@ pub fn build(b: *std.Build) !void {
220
245
  // TODO: tests can be run multiple times due to @imports
221
246
  const tests = TestStep.create(b, options, config);
222
247
 
223
- var exes = std.ArrayList(*std.Build.Step.Compile).init(b.allocator);
248
+ var exes: ArrayList(*std.Build.Step.Compile) = .empty;
224
249
  const tools: ToolConfig = .{
225
250
  .options = .{
226
251
  .showdown = showdown,
@@ -228,6 +253,7 @@ pub fn build(b: *std.Build) !void {
228
253
  .chance = chance,
229
254
  .calc = calc,
230
255
  },
256
+ .module = pkmn,
231
257
  .general = config,
232
258
  .tool = .{
233
259
  .tests = if (tests.build) tests else null,
@@ -266,6 +292,85 @@ pub fn build(b: *std.Build) !void {
266
292
  }
267
293
  }
268
294
 
295
+ fn buildWasm(
296
+ b: *std.Build,
297
+ name: []const u8,
298
+ root_src_file: []const u8,
299
+ optimize: std.builtin.OptimizeMode,
300
+ strip: ?bool,
301
+ pic: ?bool,
302
+ wasm_stack_size: u64,
303
+ import: ?*std.Build.Module,
304
+ options: anytype,
305
+ ) !void {
306
+ const mode = switch (optimize) {
307
+ .ReleaseFast, .ReleaseSafe => .ReleaseSmall,
308
+ else => optimize,
309
+ };
310
+ // https://webassembly.org/features/
311
+ var features = std.Target.wasm.featureSet(&.{
312
+ .atomics,
313
+ .bulk_memory,
314
+ // .exception_handling,
315
+ .extended_const,
316
+ // .half_precision,
317
+ // .multimemory,
318
+ .mutable_globals,
319
+ .nontrapping_fptoint,
320
+ .reference_types,
321
+ // .relaxed_simd,
322
+ .sign_ext,
323
+ .simd128,
324
+ // .tail_call,
325
+ });
326
+ if (builtin.zig_version.order(try std.SemanticVersion.parse("0.12.0")) != .lt) {
327
+ features.addFeature(@intFromEnum(std.Target.wasm.Feature.multivalue));
328
+ }
329
+ const freestanding = b.resolveTargetQuery(.{
330
+ .cpu_arch = .wasm32,
331
+ .os_tag = .freestanding,
332
+ .cpu_features_add = features,
333
+ });
334
+ const path = b.path(root_src_file);
335
+ const exe = b.addExecutable(.{
336
+ .name = name,
337
+ .root_module = b.createModule(.{
338
+ .root_source_file = path,
339
+ .optimize = mode,
340
+ .target = freestanding,
341
+ .strip = strip,
342
+ .pic = pic,
343
+ }),
344
+ });
345
+
346
+ var file = try std.Io.Dir.cwd().openFile(b.graph.io, root_src_file, .{});
347
+ defer file.close(b.graph.io);
348
+ var reader = file.reader(b.graph.io, &.{});
349
+ const bytes = try reader.interface.allocRemaining(b.allocator, .unlimited);
350
+ exe.root_module.export_symbol_names = try exports(b, bytes);
351
+ exe.entry = .disabled;
352
+
353
+ if (import) |i| {
354
+ exe.root_module.addImport("pkmn", i);
355
+ }
356
+ exe.stack_size = wasm_stack_size;
357
+ exe.root_module.addOptions("build_options", options);
358
+
359
+ const opt = b.findProgram(&.{"wasm-opt"}, &.{"./node_modules/.bin"}) catch null;
360
+ if (optimize != .Debug and opt != null) {
361
+ const out = b.fmt("build/lib/{s}.wasm", .{name});
362
+ const sh = b.addSystemCommand(&.{ opt.?, "--enable-bulk-memory", "--enable-simd", "-O4" });
363
+ sh.addArtifactArg(exe);
364
+ sh.addArg("-o");
365
+ sh.addFileArg(b.path(out));
366
+ b.getInstallStep().dependOn(&sh.step);
367
+ } else {
368
+ b.getInstallStep().dependOn(&b.addInstallArtifact(exe, .{
369
+ .dest_dir = .{ .override = std.Build.InstallDir{ .lib = {} } },
370
+ }).step);
371
+ }
372
+ }
373
+
269
374
  fn maybeStrip(
270
375
  b: *std.Build,
271
376
  artifact: *std.Build.Step.Compile,
@@ -305,16 +410,20 @@ const TestStep = struct {
305
410
  const step = std.Build.Step.init(.{ .id = .custom, .name = "Run all tests", .owner = b });
306
411
  self.* = .{ .step = step, .build = test_filter == null };
307
412
 
413
+ const path = b.path("src/lib/test.zig");
308
414
  const tests = b.addTest(.{
309
- .root_source_file = b.path("src/lib/test.zig"),
310
- .optimize = config.optimize,
311
- .target = config.target,
312
- .filter = test_filter,
313
- .single_threaded = true,
314
- .strip = config.strip,
315
- .pic = config.pic,
415
+ .root_module = b.createModule(.{
416
+ .root_source_file = path,
417
+ .optimize = config.optimize,
418
+ .target = config.target,
419
+ .single_threaded = true,
420
+ .strip = config.strip,
421
+ .pic = config.pic,
422
+ }),
423
+ .filters = if (test_filter) |filter| &.{filter} else &.{},
316
424
  });
317
425
  tests.root_module.addOptions("build_options", options);
426
+
318
427
  maybeStrip(b, tests, &tests.step, config.strip, config.cmd);
319
428
  if (coverage) |c| {
320
429
  tests.setExecCmd(&.{ "kcov", "--include-pattern=src/lib", c, null });
@@ -327,19 +436,17 @@ const TestStep = struct {
327
436
 
328
437
  const ToolConfig = struct {
329
438
  options: Options,
439
+ module: ?*std.Build.Module,
330
440
  general: Config,
331
441
  tool: struct {
332
442
  tests: ?*TestStep,
333
443
  name: ?[]const u8 = null,
334
- exes: *std.ArrayList(*std.Build.Step.Compile),
444
+ exes: *ArrayList(*std.Build.Step.Compile),
335
445
  },
336
446
  };
337
447
 
338
448
  fn tool(b: *std.Build, path: []const u8, config: ToolConfig) !?*std.Build.Step.Run {
339
- std.fs.cwd().access(path, .{}) catch |err| switch (err) {
340
- error.FileNotFound => return null,
341
- else => |e| return e,
342
- };
449
+ if (!try exists(b, path)) return null;
343
450
  var name = config.tool.name orelse std.fs.path.basename(path);
344
451
  const index = std.mem.lastIndexOfScalar(u8, name, '.');
345
452
  if (index) |i| name = name[0..i];
@@ -347,17 +454,23 @@ fn tool(b: *std.Build, path: []const u8, config: ToolConfig) !?*std.Build.Step.R
347
454
 
348
455
  const exe = b.addExecutable(.{
349
456
  .name = name,
350
- .root_source_file = b.path(path),
351
- .target = config.general.target,
352
- .optimize = config.general.optimize,
353
- .single_threaded = true,
354
- .strip = config.general.strip,
355
- .pic = config.general.pic,
457
+ .root_module = b.createModule(.{
458
+ .root_source_file = b.path(path),
459
+ .target = config.general.target,
460
+ .optimize = config.general.optimize,
461
+ .strip = config.general.strip,
462
+ .pic = config.general.pic,
463
+ }),
356
464
  });
357
- exe.root_module.addImport("pkmn", module(b, config.options));
465
+
466
+ const import = if (config.module) |m| m else module(b, config.options);
467
+ exe.root_module.addImport("pkmn", import);
468
+ if (config.general.target.result.os.tag == .windows) {
469
+ exe.root_module.linkSystemLibrary("advapi32", .{});
470
+ }
358
471
 
359
472
  if (config.tool.tests) |ts| ts.step.dependOn(&exe.step);
360
- config.tool.exes.append(exe) catch @panic("OOM");
473
+ config.tool.exes.append(b.allocator, exe) catch @panic("OOM");
361
474
 
362
475
  const run = b.addRunArtifact(exe);
363
476
  maybeStrip(b, exe, &run.step, config.general.strip, config.general.cmd);
@@ -366,10 +479,18 @@ fn tool(b: *std.Build, path: []const u8, config: ToolConfig) !?*std.Build.Step.R
366
479
  return run;
367
480
  }
368
481
 
482
+ fn exists(b: *std.Build, path: []const u8) !bool {
483
+ std.Io.Dir.cwd().access(b.graph.io, path, .{}) catch |err| switch (err) {
484
+ error.FileNotFound => return false,
485
+ else => |e| return e,
486
+ };
487
+ return true;
488
+ }
489
+
369
490
  const ToolsStep = struct {
370
491
  step: std.Build.Step,
371
492
 
372
- pub fn create(b: *std.Build, exes: *std.ArrayList(*std.Build.Step.Compile)) *ToolsStep {
493
+ pub fn create(b: *std.Build, exes: *ArrayList(*std.Build.Step.Compile)) *ToolsStep {
373
494
  const self = b.allocator.create(ToolsStep) catch @panic("OOM");
374
495
  const step = std.Build.Step.init(.{ .id = .custom, .name = "Install tools", .owner = b });
375
496
  self.* = .{ .step = step };
@@ -377,3 +498,21 @@ const ToolsStep = struct {
377
498
  return self;
378
499
  }
379
500
  };
501
+
502
+ pub fn exports(b: *std.Build, bytes: []const u8) ![][]const u8 {
503
+ var symbols: ArrayList([]const u8) = .empty;
504
+
505
+ var it = std.mem.splitSequence(u8, bytes, "export ");
506
+ _ = it.next();
507
+ while (it.next()) |s| {
508
+ if (std.mem.startsWith(u8, s, "const ")) {
509
+ const i = std.mem.indexOf(u8, s[6..], " ").?;
510
+ try symbols.append(b.allocator, s[6 .. 6 + i]);
511
+ } else { // "fn "
512
+ const i = std.mem.indexOf(u8, s[3..], "(").?;
513
+ try symbols.append(b.allocator, s[3 .. 3 + i]);
514
+ }
515
+ }
516
+
517
+ return symbols.items;
518
+ }