@pkmn/engine 0.1.0-dev.47dcb76c → 0.1.0-dev.47f842bf

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 +65 -15
  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 +25 -3
  13. package/build/pkg/addon.js +39 -71
  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 +25 -4
  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 +109 -107
  27. package/build/pkg/protocol.js.map +1 -1
  28. package/build/tools/debug.js +53 -78
  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 +309 -79
  39. package/package.json +55 -135
  40. package/src/bin/install-pkmn-engine +19 -8
  41. package/src/bin/pkmn-debug +1 -1
  42. package/src/lib/bindings/c.zig +255 -0
  43. package/src/lib/bindings/node.zig +117 -0
  44. package/src/lib/bindings/wasm.zig +59 -0
  45. package/src/lib/common/array.zig +77 -0
  46. package/src/lib/common/data.zig +8 -8
  47. package/src/lib/common/debug.zig +39 -8
  48. package/src/lib/common/js.zig +4 -6
  49. package/src/lib/common/napi.zig +4 -0
  50. package/src/lib/common/optional.zig +13 -9
  51. package/src/lib/common/options.zig +6 -0
  52. package/src/lib/common/protocol.zig +50 -44
  53. package/src/lib/common/rational.zig +406 -166
  54. package/src/lib/common/rng.zig +5 -7
  55. package/src/lib/common/util.zig +65 -1
  56. package/src/lib/gen1/calc.zig +695 -159
  57. package/src/lib/gen1/chance.zig +415 -290
  58. package/src/lib/gen1/data/moves.zig +2 -4
  59. package/src/lib/gen1/data/species.zig +1 -3
  60. package/src/lib/gen1/data/types.zig +2 -3
  61. package/src/lib/gen1/data.zig +38 -41
  62. package/src/lib/gen1/helpers.zig +92 -548
  63. package/src/lib/gen1/mechanics.zig +342 -245
  64. package/src/lib/gen2/calc.zig +37 -27
  65. package/src/lib/gen2/chance.zig +90 -502
  66. package/src/lib/gen2/data/items.zig +1 -3
  67. package/src/lib/gen2/data/moves.zig +2 -4
  68. package/src/lib/gen2/data/species.zig +1 -3
  69. package/src/lib/gen2/data/types.zig +7 -5
  70. package/src/lib/gen2/data.zig +28 -30
  71. package/src/lib/gen2/generated.zig +18 -23
  72. package/src/lib/gen2/helpers.zig +46 -25
  73. package/src/lib/gen2/mechanics.zig +199 -141
  74. package/src/lib/node.zig +4 -121
  75. package/src/lib/pkmn.zig +17 -18
  76. package/src/lib/wasm.zig +13 -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,5 @@
1
- const std = @import("std");
2
1
  const builtin = @import("builtin");
2
+ const std = @import("std");
3
3
 
4
4
  pub const Options = struct {
5
5
  showdown: ?bool = null,
@@ -8,6 +8,17 @@ pub const Options = struct {
8
8
  calc: ?bool = null,
9
9
  };
10
10
 
11
+ const Step = if (@hasDecl(std.Build, "Step")) std.Build.Step else .{
12
+ .Compile = std.Build.CompileStep,
13
+ .Options = std.Build.OptionsStep,
14
+ .Run = std.Build.RunStep,
15
+ };
16
+ const has_path = @hasField(std.Build.LazyPath, "path");
17
+ const root_module = @hasField(Step.Compile, "root_module");
18
+ const force_pic = !@hasField(std.Build.SharedLibraryOptions, "pic");
19
+ const ResolvedTarget =
20
+ if (@hasDecl(std.Build, "ResolvedTarget")) std.Build.ResolvedTarget else std.zig.CrossTarget;
21
+
11
22
  pub fn module(b: *std.Build, options: Options) *std.Build.Module {
12
23
  const dirname = comptime std.fs.path.dirname(@src().file) orelse ".";
13
24
  const build_options = b.addOptions();
@@ -15,25 +26,39 @@ pub fn module(b: *std.Build, options: Options) *std.Build.Module {
15
26
  build_options.addOption(?bool, "log", options.log);
16
27
  build_options.addOption(?bool, "chance", options.chance);
17
28
  build_options.addOption(?bool, "calc", options.calc);
18
- return b.createModule(.{
19
- .root_source_file = .{ .path = dirname ++ "/src/lib/pkmn.zig" },
20
- .imports = &.{.{ .name = "build_options", .module = build_options.createModule() }},
21
- });
29
+
30
+ const root_source_file: std.Build.LazyPath =
31
+ .{ .cwd_relative = dirname ++ "/src/lib/pkmn.zig" };
32
+ const Import = if (@hasDecl(std.Build.Module, "Import"))
33
+ std.Build.Module.Import
34
+ else
35
+ std.Build.ModuleDependency;
36
+ const imports: []const Import =
37
+ &.{.{ .name = "build_options", .module = build_options.createModule() }};
38
+ return b.createModule(if (@hasDecl(std.Build, "CreateModuleOptions"))
39
+ .{ .source_file = root_source_file, .dependencies = imports }
40
+ else
41
+ .{ .root_source_file = root_source_file, .imports = imports });
22
42
  }
23
43
 
24
44
  pub fn build(b: *std.Build) !void {
25
45
  const target = b.standardTargetOptions(.{});
26
46
  const optimize = b.standardOptimizeOption(.{});
27
47
 
48
+ const default: ?bool = if (force_pic) false else null;
28
49
  const node_headers = b.option([]const u8, "node-headers", "Path to node-headers");
29
50
  const node_import_lib =
30
51
  b.option([]const u8, "node-import-library", "Path to node import library (Windows)");
52
+ const demo = if (try exists("src/tools/demo.zig"))
53
+ b.option(bool, "demo", "Build the demo library") orelse false
54
+ else
55
+ false;
31
56
  const wasm = b.option(bool, "wasm", "Build a WASM library") orelse false;
32
57
  const wasm_stack_size =
33
58
  b.option(u64, "wasm-stack-size", "The size of WASM stack") orelse std.wasm.page_size;
34
59
  const dynamic = b.option(bool, "dynamic", "Build a dynamic library") orelse false;
35
- const strip = b.option(bool, "strip", "Strip debugging symbols from binary");
36
- const pic = b.option(bool, "pic", "Force position independent code");
60
+ const strip = b.option(bool, "strip", "Strip debugging symbols from binary") orelse default;
61
+ const pic = b.option(bool, "pic", "Force position independent code") orelse default;
37
62
  const emit_asm = b.option(bool, "emit-asm", "Output .s (assembly code)") orelse false;
38
63
  const emit_ll = b.option(bool, "emit-ll", "Output .ll (LLVM IR)") orelse false;
39
64
 
@@ -44,14 +69,22 @@ pub fn build(b: *std.Build) !void {
44
69
  defer parsed.deinit();
45
70
  const version = parsed.value.object.get("version").?.string;
46
71
  const description = parsed.value.object.get("description").?.string;
47
- var repository = std.mem.split(u8, parsed.value.object.get("repository").?.string, ":");
72
+ var repository = std.mem.splitSequence(u8, parsed.value.object.get("repository").?.string, ":");
48
73
  std.debug.assert(std.mem.eql(u8, repository.first(), "github"));
49
74
 
75
+ const fast = try std.SemanticVersion.parse("0.12.0-dev.866+3a47bc715");
76
+ const patched = std.mem.endsWith(u8, builtin.zig_version_string, "patched");
77
+ if (optimize != .Debug and fast.order(builtin.zig_version) == .lt and !patched) {
78
+ std.log.warn("Release builds are ~10-20% slower than before Zig " ++
79
+ "v0.12.0-dev.866+3a47bc715 due to ziglang/zig#17768", .{});
80
+ }
81
+
50
82
  const showdown =
51
83
  b.option(bool, "showdown", "Enable Pokémon Showdown compatibility mode") orelse false;
52
84
  const log = b.option(bool, "log", "Enable protocol message logging") orelse false;
53
85
  const chance = b.option(bool, "chance", "Enable update probability tracking") orelse false;
54
86
  const calc = b.option(bool, "calc", "Enable damage calculator support") orelse false;
87
+ const extra = b.option([]const []const u8, "option", "Enable extra option");
55
88
 
56
89
  const options = b.addOptions();
57
90
  options.addOption(?bool, "showdown", showdown);
@@ -59,30 +92,67 @@ pub fn build(b: *std.Build) !void {
59
92
  options.addOption(?bool, "chance", chance);
60
93
  options.addOption(?bool, "calc", calc);
61
94
 
95
+ if (extra) |opts| {
96
+ for (opts) |opt| {
97
+ if (std.mem.indexOfScalar(u8, opt, '=')) |i| {
98
+ options.addOption(?bool, opt[0..i], if (std.mem.eql(u8, opt[i + 1 ..], "true"))
99
+ true
100
+ else if (std.mem.eql(u8, opt[i + 1 ..], "false"))
101
+ false
102
+ else {
103
+ std.log.err("Invalid option: {s}\n", .{opt});
104
+ return error.InvalidOption;
105
+ });
106
+ } else {
107
+ options.addOption(?bool, opt, true);
108
+ }
109
+ }
110
+ }
111
+
62
112
  const name = if (showdown) "pkmn-showdown" else "pkmn";
63
113
 
114
+ const pkmn: ?*std.Build.Module = if (@hasDecl(std.Build, "CreateModuleOptions"))
115
+ null
116
+ else
117
+ b.addModule("pkmn", .{
118
+ .root_source_file = if (has_path)
119
+ .{ .path = "src/lib/pkmn.zig" }
120
+ else
121
+ b.path("src/lib/pkmn.zig"),
122
+ .optimize = optimize,
123
+ .target = target,
124
+ .imports = &.{.{ .name = "build_options", .module = options.createModule() }},
125
+ });
126
+
64
127
  var c = false;
65
128
  if (node_headers) |headers| {
66
129
  const addon = b.fmt("{s}.node", .{name});
67
- const lib = b.addSharedLibrary(.{
130
+ const path = if (has_path) .{ .path = "src/lib/node.zig" } else b.path("src/lib/node.zig");
131
+ const lib = b.addSharedLibrary(if (force_pic) .{
132
+ .name = addon,
133
+ .root_source_file = path,
134
+ .optimize = optimize,
135
+ .target = target,
136
+ } else .{
68
137
  .name = addon,
69
- .root_source_file = .{ .path = "src/lib/node.zig" },
138
+ .root_source_file = path,
70
139
  .optimize = optimize,
71
140
  .target = target,
72
141
  .strip = strip,
73
142
  .pic = pic,
74
143
  });
75
- lib.root_module.addOptions("build_options", options);
76
- lib.addSystemIncludePath(.{ .path = headers });
144
+ (if (root_module) lib.root_module else lib).addOptions("build_options", options);
145
+ lib.addSystemIncludePath(if (has_path) .{ .path = headers } else b.path(headers));
77
146
  lib.linkLibC();
78
147
  if (node_import_lib) |il| {
79
- lib.addObjectFile(.{ .path = il });
80
- } else if (target.result.os.tag == .windows) {
148
+ lib.addObjectFile(if (has_path) .{ .path = il } else b.path(il));
149
+ } else if (detect(target).os.tag == .windows) {
81
150
  try std.io.getStdErr().writeAll("Must provide --node-import-library path on Windows\n");
82
151
  std.process.exit(1);
83
152
  }
84
153
  lib.linker_allow_shlib_undefined = true;
85
154
  maybeStrip(b, lib, b.getInstallStep(), strip, cmd);
155
+ if (force_pic and pic orelse false) lib.force_pic = pic;
86
156
  // Always emit to build/lib because this is where the driver code expects to find it
87
157
  // TODO: ziglang/zig#2231 - using the following used to work (though was hacky):
88
158
  //
@@ -93,69 +163,61 @@ pub fn build(b: *std.Build) !void {
93
163
  // rename the file ourself in install-pkmn-engine
94
164
  b.installArtifact(lib);
95
165
  } else if (wasm) {
96
- const exe = b.addExecutable(.{
97
- .name = name,
98
- .root_source_file = .{ .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,
166
+ const path = "src/lib/wasm.zig";
167
+ try buildWasm(b, name, path, optimize, strip, pic, wasm_stack_size, null, options);
168
+ } else if (demo) {
169
+ const path = "src/tools/demo.zig";
170
+ const n = if (showdown) "demo-showdown" else "demo";
171
+ const mod = pkmn orelse module(b, .{
172
+ .showdown = showdown,
173
+ .log = log,
174
+ .chance = chance,
175
+ .calc = calc,
106
176
  });
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(.{ .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
- }
177
+ try buildWasm(b, n, path, optimize, strip, pic, wasm_stack_size, mod, options);
131
178
  } else if (dynamic) {
132
- const lib = b.addSharedLibrary(.{
179
+ const path = if (has_path) .{ .path = "src/lib/c.zig" } else b.path("src/lib/c.zig");
180
+ const lib = b.addSharedLibrary(if (force_pic) .{
181
+ .name = name,
182
+ .root_source_file = path,
183
+ .version = try std.SemanticVersion.parse(version),
184
+ .optimize = optimize,
185
+ .target = target,
186
+ } else .{
133
187
  .name = name,
134
- .root_source_file = .{ .path = "src/lib/c.zig" },
188
+ .root_source_file = path,
135
189
  .version = try std.SemanticVersion.parse(version),
136
190
  .optimize = optimize,
137
191
  .target = target,
138
192
  .strip = strip,
139
193
  .pic = pic,
140
194
  });
141
- lib.root_module.addOptions("build_options", options);
142
- lib.addIncludePath(.{ .path = "src/include" });
195
+ (if (root_module) lib.root_module else lib).addOptions("build_options", options);
196
+ lib.addIncludePath(if (has_path) .{ .path = "src/include" } else b.path("src/include"));
143
197
  maybeStrip(b, lib, b.getInstallStep(), strip, cmd);
198
+ if (force_pic and pic orelse false) lib.force_pic = pic;
144
199
  b.installArtifact(lib);
145
200
  c = true;
146
201
  } else {
147
- const lib = b.addStaticLibrary(.{
202
+ const path = if (has_path) .{ .path = "src/lib/c.zig" } else b.path("src/lib/c.zig");
203
+ const lib = b.addStaticLibrary(if (force_pic) .{
148
204
  .name = name,
149
- .root_source_file = .{ .path = "src/lib/c.zig" },
205
+ .root_source_file = path,
206
+ .optimize = optimize,
207
+ .target = target,
208
+ } else .{
209
+ .name = name,
210
+ .root_source_file = path,
150
211
  .optimize = optimize,
151
212
  .target = target,
152
213
  .strip = strip,
153
214
  .pic = pic,
154
215
  });
155
- lib.root_module.addOptions("build_options", options);
156
- lib.addIncludePath(.{ .path = "src/include" });
216
+ (if (root_module) lib.root_module else lib).addOptions("build_options", options);
217
+ lib.addIncludePath(if (has_path) .{ .path = "src/include" } else b.path("src/include"));
157
218
  lib.bundle_compiler_rt = true;
158
219
  maybeStrip(b, lib, b.getInstallStep(), strip, cmd);
220
+ if (force_pic and pic orelse false) lib.force_pic = pic;
159
221
  if (emit_asm) {
160
222
  b.getInstallStep().dependOn(&b.addInstallFileWithDir(
161
223
  lib.getEmittedAsm(),
@@ -175,11 +237,9 @@ pub fn build(b: *std.Build) !void {
175
237
  }
176
238
 
177
239
  if (c) {
178
- const header = b.addInstallFileWithDir(
179
- .{ .path = "src/include/pkmn.h" },
180
- .header,
181
- "pkmn.h",
182
- );
240
+ const path =
241
+ if (has_path) .{ .path = "src/include/pkmn.h" } else b.path("src/include/pkmn.h");
242
+ const header = b.addInstallFileWithDir(path, .header, "pkmn.h");
183
243
  b.getInstallStep().dependOn(&header.step);
184
244
 
185
245
  const pc = b.fmt("lib{s}.pc", .{name});
@@ -209,7 +269,7 @@ pub fn build(b: *std.Build) !void {
209
269
  b.installFile(file, b.fmt("share/pkgconfig/{s}", .{pc}));
210
270
  }
211
271
 
212
- const config = .{
272
+ const config: Config = .{
213
273
  .target = target,
214
274
  .optimize = optimize,
215
275
  .pic = pic,
@@ -220,7 +280,7 @@ pub fn build(b: *std.Build) !void {
220
280
  // TODO: tests can be run multiple times due to @imports
221
281
  const tests = TestStep.create(b, options, config);
222
282
 
223
- var exes = std.ArrayList(*std.Build.Step.Compile).init(b.allocator);
283
+ var exes = std.ArrayList(*Step.Compile).init(b.allocator);
224
284
  const tools: ToolConfig = .{
225
285
  .options = .{
226
286
  .showdown = showdown,
@@ -228,6 +288,7 @@ pub fn build(b: *std.Build) !void {
228
288
  .chance = chance,
229
289
  .calc = calc,
230
290
  },
291
+ .module = pkmn,
231
292
  .general = config,
232
293
  .tool = .{
233
294
  .tests = if (tests.build) tests else null,
@@ -266,13 +327,127 @@ pub fn build(b: *std.Build) !void {
266
327
  }
267
328
  }
268
329
 
330
+ fn buildWasm(
331
+ b: *std.Build,
332
+ name: []const u8,
333
+ root_src_file: []const u8,
334
+ optimize: std.builtin.OptimizeMode,
335
+ strip: ?bool,
336
+ pic: ?bool,
337
+ wasm_stack_size: u64,
338
+ import: ?*std.Build.Module,
339
+ options: anytype,
340
+ ) !void {
341
+ const entry = @hasDecl(Step.Compile, "Entry");
342
+ const mode = switch (optimize) {
343
+ .ReleaseFast, .ReleaseSafe => .ReleaseSmall,
344
+ else => optimize,
345
+ };
346
+ // https://webassembly.org/features/
347
+ var features = std.Target.wasm.featureSet(&.{
348
+ .atomics,
349
+ .bulk_memory,
350
+ // .exception_handling,
351
+ .extended_const,
352
+ // .half_precision,
353
+ // .multimemory,
354
+ .mutable_globals,
355
+ .nontrapping_fptoint,
356
+ .reference_types,
357
+ // .relaxed_simd,
358
+ .sign_ext,
359
+ .simd128,
360
+ // .tail_call,
361
+ });
362
+ if (builtin.zig_version.order(try std.SemanticVersion.parse("0.12.0")) != .lt) {
363
+ features.addFeature(@intFromEnum(std.Target.wasm.Feature.multivalue));
364
+ }
365
+ const freestanding = if (@hasDecl(std.Build, "resolveTargetQuery"))
366
+ b.resolveTargetQuery(.{
367
+ .cpu_arch = .wasm32,
368
+ .os_tag = .freestanding,
369
+ .cpu_features_add = features,
370
+ })
371
+ else
372
+ ResolvedTarget{
373
+ .cpu_arch = .wasm32,
374
+ .os_tag = .freestanding,
375
+ .cpu_features_add = features,
376
+ };
377
+ const path = if (has_path) .{ .path = root_src_file } else b.path(root_src_file);
378
+ const bin = if (entry) bin: {
379
+ const exe = b.addExecutable(if (force_pic) .{
380
+ .name = name,
381
+ .root_source_file = path,
382
+ .optimize = mode,
383
+ .target = freestanding,
384
+ } else .{
385
+ .name = name,
386
+ .root_source_file = path,
387
+ .optimize = mode,
388
+ .target = freestanding,
389
+ .strip = strip,
390
+ .pic = pic,
391
+ });
392
+
393
+ var file = try std.fs.cwd().openFile(root_src_file, .{});
394
+ defer file.close();
395
+ const bytes = try file.readToEndAlloc(b.allocator, std.math.maxInt(usize));
396
+ (if (root_module) exe.root_module else exe).export_symbol_names = try exports(b, bytes);
397
+ exe.entry = .disabled;
398
+ break :bin exe;
399
+ } else bin: {
400
+ const lib = b.addSharedLibrary(.{
401
+ .name = name,
402
+ .root_source_file = path,
403
+ .optimize = mode,
404
+ .target = freestanding,
405
+ });
406
+ lib.rdynamic = true;
407
+ break :bin lib;
408
+ };
409
+ if (import) |i| {
410
+ if (root_module) {
411
+ bin.root_module.addImport("pkmn", i);
412
+ } else {
413
+ bin.addModule("pkmn", i);
414
+ }
415
+ }
416
+ bin.stack_size = wasm_stack_size;
417
+ if (@hasDecl(@TypeOf(bin.*), "strip")) bin.strip = strip orelse false;
418
+ if (force_pic and pic orelse false) bin.force_pic = pic;
419
+ (if (root_module) bin.root_module else bin).addOptions("build_options", options);
420
+
421
+ const opt = b.findProgram(&.{"wasm-opt"}, &.{"./node_modules/.bin"}) catch null;
422
+ if (optimize != .Debug and opt != null) {
423
+ const out = b.fmt("build/lib/{s}.wasm", .{name});
424
+ const sh = b.addSystemCommand(&.{ opt.?, "--enable-bulk-memory", "--enable-simd", "-O4" });
425
+ sh.addArtifactArg(bin);
426
+ sh.addArg("-o");
427
+ if (@hasDecl(@TypeOf(sh.*), "addFileSourceArg")) {
428
+ sh.addFileSourceArg(if (has_path) .{ .path = out } else b.path(out));
429
+ } else {
430
+ sh.addFileArg(if (has_path) .{ .path = out } else b.path(out));
431
+ }
432
+ b.getInstallStep().dependOn(&sh.step);
433
+ if (!entry) b.installArtifact(bin);
434
+ } else if (entry) {
435
+ b.getInstallStep().dependOn(&b.addInstallArtifact(bin, .{
436
+ .dest_dir = .{ .override = std.Build.InstallDir{ .lib = {} } },
437
+ }).step);
438
+ } else {
439
+ b.installArtifact(bin);
440
+ }
441
+ }
442
+
269
443
  fn maybeStrip(
270
444
  b: *std.Build,
271
- artifact: *std.Build.Step.Compile,
445
+ artifact: *Step.Compile,
272
446
  step: *std.Build.Step,
273
447
  strip: ?bool,
274
448
  cmd: ?[]const u8,
275
449
  ) void {
450
+ if (@hasDecl(@TypeOf(artifact.*), "strip")) artifact.strip = strip orelse false;
276
451
  if (!(strip orelse false) or cmd == null) return;
277
452
  // Using `strip -r -u` for dynamic libraries is supposed to work on macOS but doesn't...
278
453
  const mac = builtin.os.tag == .macos;
@@ -285,7 +460,7 @@ fn maybeStrip(
285
460
  }
286
461
 
287
462
  const Config = struct {
288
- target: std.Build.ResolvedTarget,
463
+ target: ResolvedTarget,
289
464
  optimize: std.builtin.OptimizeMode,
290
465
  pic: ?bool,
291
466
  strip: ?bool,
@@ -296,7 +471,7 @@ const TestStep = struct {
296
471
  step: std.Build.Step,
297
472
  build: bool,
298
473
 
299
- pub fn create(b: *std.Build, options: *std.Build.Step.Options, config: Config) *TestStep {
474
+ pub fn create(b: *std.Build, options: *Step.Options, config: Config) *TestStep {
300
475
  const coverage = b.option([]const u8, "test-coverage", "Generate test coverage");
301
476
  const test_filter =
302
477
  b.option([]const u8, "test-filter", "Skip tests that do not match filter");
@@ -305,8 +480,14 @@ const TestStep = struct {
305
480
  const step = std.Build.Step.init(.{ .id = .custom, .name = "Run all tests", .owner = b });
306
481
  self.* = .{ .step = step, .build = test_filter == null };
307
482
 
308
- const tests = b.addTest(.{
309
- .root_source_file = .{ .path = "src/lib/test.zig" },
483
+ const path = if (has_path) .{ .path = "src/lib/test.zig" } else b.path("src/lib/test.zig");
484
+ const tests = b.addTest(if (force_pic) .{
485
+ .root_source_file = path,
486
+ .optimize = config.optimize,
487
+ .target = config.target,
488
+ .filter = test_filter,
489
+ } else .{
490
+ .root_source_file = path,
310
491
  .optimize = config.optimize,
311
492
  .target = config.target,
312
493
  .filter = test_filter,
@@ -314,8 +495,13 @@ const TestStep = struct {
314
495
  .strip = config.strip,
315
496
  .pic = config.pic,
316
497
  });
317
- tests.root_module.addOptions("build_options", options);
498
+ (if (root_module) tests.root_module else tests).addOptions("build_options", options);
499
+
318
500
  maybeStrip(b, tests, &tests.step, config.strip, config.cmd);
501
+ if (force_pic) {
502
+ tests.single_threaded = true;
503
+ if (config.pic orelse false) tests.force_pic = config.pic;
504
+ }
319
505
  if (coverage) |c| {
320
506
  tests.setExecCmd(&.{ "kcov", "--include-pattern=src/lib", c, null });
321
507
  }
@@ -327,35 +513,48 @@ const TestStep = struct {
327
513
 
328
514
  const ToolConfig = struct {
329
515
  options: Options,
516
+ module: ?*std.Build.Module,
330
517
  general: Config,
331
518
  tool: struct {
332
519
  tests: ?*TestStep,
333
520
  name: ?[]const u8 = null,
334
- exes: *std.ArrayList(*std.Build.Step.Compile),
521
+ exes: *std.ArrayList(*Step.Compile),
335
522
  },
336
523
  };
337
524
 
338
- 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
- };
525
+ fn tool(b: *std.Build, path: []const u8, config: ToolConfig) !?*Step.Run {
526
+ if (!try exists(path)) return null;
343
527
  var name = config.tool.name orelse std.fs.path.basename(path);
344
528
  const index = std.mem.lastIndexOfScalar(u8, name, '.');
345
529
  if (index) |i| name = name[0..i];
346
530
  if (config.options.showdown orelse false) name = b.fmt("{s}-showdown", .{name});
347
531
 
348
- const exe = b.addExecutable(.{
532
+ const exe = b.addExecutable(if (force_pic) .{
533
+ .name = name,
534
+ .root_source_file = if (has_path) .{ .path = path } else b.path(path),
535
+ .target = config.general.target,
536
+ .optimize = config.general.optimize,
537
+ } else .{
349
538
  .name = name,
350
- .root_source_file = .{ .path = path },
539
+ .root_source_file = if (has_path) .{ .path = path } else b.path(path),
351
540
  .target = config.general.target,
352
541
  .optimize = config.general.optimize,
353
542
  .single_threaded = true,
354
543
  .strip = config.general.strip,
355
544
  .pic = config.general.pic,
356
545
  });
357
- exe.root_module.addImport("pkmn", module(b, config.options));
358
546
 
547
+ if (root_module) {
548
+ const import = if (config.module) |m| m else module(b, config.options);
549
+ exe.root_module.addImport("pkmn", import);
550
+ } else {
551
+ exe.addModule("pkmn", module(b, config.options));
552
+ }
553
+
554
+ if (force_pic) {
555
+ exe.single_threaded = true;
556
+ if (config.general.pic orelse false) exe.force_pic = config.general.pic;
557
+ }
359
558
  if (config.tool.tests) |ts| ts.step.dependOn(&exe.step);
360
559
  config.tool.exes.append(exe) catch @panic("OOM");
361
560
 
@@ -366,10 +565,18 @@ fn tool(b: *std.Build, path: []const u8, config: ToolConfig) !?*std.Build.Step.R
366
565
  return run;
367
566
  }
368
567
 
568
+ fn exists(path: []const u8) !bool {
569
+ std.fs.cwd().access(path, .{}) catch |err| switch (err) {
570
+ error.FileNotFound => return false,
571
+ else => |e| return e,
572
+ };
573
+ return true;
574
+ }
575
+
369
576
  const ToolsStep = struct {
370
577
  step: std.Build.Step,
371
578
 
372
- pub fn create(b: *std.Build, exes: *std.ArrayList(*std.Build.Step.Compile)) *ToolsStep {
579
+ pub fn create(b: *std.Build, exes: *std.ArrayList(*Step.Compile)) *ToolsStep {
373
580
  const self = b.allocator.create(ToolsStep) catch @panic("OOM");
374
581
  const step = std.Build.Step.init(.{ .id = .custom, .name = "Install tools", .owner = b });
375
582
  self.* = .{ .step = step };
@@ -377,3 +584,26 @@ const ToolsStep = struct {
377
584
  return self;
378
585
  }
379
586
  };
587
+
588
+ fn detect(target: anytype) std.Target {
589
+ if (@hasField(@TypeOf(target), "result")) return target.result;
590
+ return (std.zig.system.NativeTargetInfo.detect(target) catch unreachable).target;
591
+ }
592
+
593
+ pub fn exports(b: *std.Build, bytes: []const u8) ![][]const u8 {
594
+ var symbols = std.ArrayList([]const u8).init(b.allocator);
595
+
596
+ var it = std.mem.splitSequence(u8, bytes, "export ");
597
+ _ = it.next();
598
+ while (it.next()) |s| {
599
+ if (std.mem.startsWith(u8, s, "const ")) {
600
+ const i = std.mem.indexOf(u8, s[6..], " ").?;
601
+ try symbols.append(s[6 .. 6 + i]);
602
+ } else { // "fn "
603
+ const i = std.mem.indexOf(u8, s[3..], "(").?;
604
+ try symbols.append(s[3 .. 3 + i]);
605
+ }
606
+ }
607
+
608
+ return symbols.items;
609
+ }