Skip to content

Commit 5f5a64d

Browse files
committed
Dynamically get entrypoints
1 parent c1b7650 commit 5f5a64d

File tree

7 files changed

+374
-46
lines changed

7 files changed

+374
-46
lines changed

build.zig

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,25 @@ pub fn build(b: *std.Build) void {
547547
tests_summary.addRun(&run_roc_subcommands_test.step);
548548
}
549549

550+
// fx platform effectful functions test
551+
{
552+
const fx_platform_test = b.addTest(.{
553+
.name = "fx_platform_test",
554+
.root_module = b.createModule(.{
555+
.root_source_file = b.path("src/cli/test/fx_platform_test.zig"),
556+
.target = target,
557+
.optimize = optimize,
558+
}),
559+
.filters = test_filters,
560+
});
561+
562+
const run_fx_platform_test = b.addRunArtifact(fx_platform_test);
563+
if (run_args.len != 0) {
564+
run_fx_platform_test.addArgs(run_args);
565+
}
566+
tests_summary.addRun(&run_fx_platform_test.step);
567+
}
568+
550569
// Add watch tests
551570
const enable_watch_tests = b.option(bool, "watch-tests", "Enable watch tests") orelse true;
552571
if (enable_watch_tests) {
@@ -809,6 +828,22 @@ fn addMainExe(
809828
copy_test_int_host.addCopyFileToSource(test_platform_int_host_lib.getEmittedBin(), b.pathJoin(&.{ "test/int/platform", test_int_host_filename }));
810829
b.getInstallStep().dependOn(&copy_test_int_host.step);
811830

831+
// Create test platform host static library (fx) - native target
832+
const test_platform_fx_host_lib = createTestPlatformHostLib(
833+
b,
834+
"test_platform_fx_host",
835+
"test/fx/platform/host.zig",
836+
target,
837+
optimize,
838+
roc_modules,
839+
);
840+
841+
// Copy the fx test platform host library to the source directory
842+
const copy_test_fx_host = b.addUpdateSourceFiles();
843+
const test_fx_host_filename = if (target.result.os.tag == .windows) "host.lib" else "libhost.a";
844+
copy_test_fx_host.addCopyFileToSource(test_platform_fx_host_lib.getEmittedBin(), b.pathJoin(&.{ "test/fx/platform", test_fx_host_filename }));
845+
b.getInstallStep().dependOn(&copy_test_fx_host.step);
846+
812847
// Cross-compile int platform host libraries for musl and glibc targets
813848
const cross_compile_targets = [_]struct { name: []const u8, query: std.Target.Query }{
814849
.{ .name = "x64musl", .query = .{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl } },

src/cli/app_stub.zig

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -176,22 +176,6 @@ fn addRocCallAbiStub(
176176
try wip.finish();
177177
}
178178

179-
/// Get the expected app entrypoints for known test platforms based on host.zig files
180-
pub fn getTestPlatformEntrypoints(allocator: Allocator, platform_type: []const u8) ![]PlatformEntrypoint {
181-
if (std.mem.eql(u8, platform_type, "int")) {
182-
// Based on test/int/platform/host.zig:
183-
// extern fn roc__addInts(ops: *builtins.host_abi.RocOps, ret_ptr: *anyopaque, arg_ptr: ?*anyopaque) callconv(.c) void;
184-
// extern fn roc__multiplyInts(ops: *builtins.host_abi.RocOps, ret_ptr: *anyopaque, arg_ptr: ?*anyopaque) callconv(.c) void;
185-
const entrypoints = try allocator.alloc(PlatformEntrypoint, 2);
186-
entrypoints[0] = PlatformEntrypoint{ .name = "addInts" };
187-
entrypoints[1] = PlatformEntrypoint{ .name = "multiplyInts" };
188-
return entrypoints;
189-
}
190-
191-
// Only int platform supported for cross-compilation
192-
return error.PlatformNotSupported;
193-
}
194-
195179
/// Detect platform type from file path
196180
pub fn detectPlatformType(platform_path: []const u8) []const u8 {
197181
// Use cross-platform path checking

src/cli/main.zig

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2166,35 +2166,15 @@ fn rocBuild(allocs: *Allocators, args: cli_args.BuildArgs) !void {
21662166
},
21672167
}
21682168

2169-
// Only support int test platform for cross-compilation
2170-
// Check if path contains "int" directory using cross-platform path handling
2171-
const path_contains_int = blk: {
2172-
var iter = std.fs.path.componentIterator(args.path) catch break :blk false;
2173-
while (iter.next()) |component| {
2174-
if (std.mem.eql(u8, component.name, "int")) {
2175-
break :blk true;
2176-
}
2177-
}
2178-
break :blk false;
2179-
};
2180-
2181-
const platform_type = if (path_contains_int)
2182-
"int"
2183-
else {
2184-
std.log.err("roc build cross-compilation currently only supports the int test platform", .{});
2185-
std.log.err("Your app path: {s}", .{args.path});
2186-
std.log.err("For str platform and other platforms, please use regular 'roc' command", .{});
2187-
return error.UnsupportedPlatform;
2169+
// Detect platform directory from app path (e.g., test/int/app.roc -> test/int/platform)
2170+
const platform_dir = blk: {
2171+
const app_dir = std.fs.path.dirname(args.path) orelse {
2172+
std.log.err("Could not determine directory from app path: {s}", .{args.path});
2173+
return error.InvalidAppPath;
2174+
};
2175+
break :blk try std.fs.path.join(allocs.arena, &.{ app_dir, "platform" });
21882176
};
21892177

2190-
std.log.info("Detected platform type: {s}", .{platform_type});
2191-
2192-
// Get platform directory path
2193-
const platform_dir = if (std.mem.eql(u8, platform_type, "int"))
2194-
try std.fs.path.join(allocs.arena, &.{ "test", "int", "platform" })
2195-
else
2196-
try std.fs.path.join(allocs.arena, &.{ "test", "str", "platform" });
2197-
21982178
// Check that platform exists
21992179
std.fs.cwd().access(platform_dir, .{}) catch |err| {
22002180
std.log.err("Platform directory not found: {s} ({})", .{ platform_dir, err });
@@ -2220,9 +2200,18 @@ fn rocBuild(allocs: *Allocators, args: cli_args.BuildArgs) !void {
22202200
return err;
22212201
};
22222202

2223-
// Get expected entrypoints for this platform
2224-
const entrypoints = try app_stub.getTestPlatformEntrypoints(allocs.gpa, platform_type);
2225-
defer allocs.gpa.free(entrypoints);
2203+
// Get expected entrypoints by parsing the platform's main.roc file
2204+
const platform_source_path = try std.fs.path.join(allocs.arena, &.{ platform_dir, "main.roc" });
2205+
var entrypoints_list = std.array_list.Managed([]const u8).init(allocs.arena);
2206+
defer entrypoints_list.deinit();
2207+
2208+
try extractEntrypointsFromPlatform(allocs, platform_source_path, &entrypoints_list);
2209+
2210+
// Convert to PlatformEntrypoint array for generateAppStubObject
2211+
const entrypoints = try allocs.arena.alloc(app_stub.PlatformEntrypoint, entrypoints_list.items.len);
2212+
for (entrypoints_list.items, 0..) |name, i| {
2213+
entrypoints[i] = app_stub.PlatformEntrypoint{ .name = name };
2214+
}
22262215

22272216
std.log.info("Expected entrypoints: {}", .{entrypoints.len});
22282217
for (entrypoints, 0..) |ep, i| {

src/cli/test/fx_platform_test.zig

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
const std = @import("std");
2+
const testing = std.testing;
3+
4+
test "fx platform effectful functions" {
5+
const allocator = testing.allocator;
6+
7+
// Build the fx app
8+
const build_result = try std.process.Child.run(.{
9+
.allocator = allocator,
10+
.argv = &[_][]const u8{
11+
"./zig-out/bin/roc",
12+
"build",
13+
"test/fx/app.roc",
14+
},
15+
});
16+
defer allocator.free(build_result.stdout);
17+
defer allocator.free(build_result.stderr);
18+
19+
switch (build_result.term) {
20+
.Exited => |code| {
21+
if (code != 0) {
22+
std.debug.print("Build failed with exit code {}\n", .{code});
23+
std.debug.print("STDOUT: {s}\n", .{build_result.stdout});
24+
std.debug.print("STDERR: {s}\n", .{build_result.stderr});
25+
return error.BuildFailed;
26+
}
27+
},
28+
else => {
29+
std.debug.print("Build terminated abnormally: {}\n", .{build_result.term});
30+
std.debug.print("STDOUT: {s}\n", .{build_result.stdout});
31+
std.debug.print("STDERR: {s}\n", .{build_result.stderr});
32+
return error.BuildFailed;
33+
},
34+
}
35+
36+
// Run the app and capture stdout/stderr separately
37+
const run_result = try std.process.Child.run(.{
38+
.allocator = allocator,
39+
.argv = &[_][]const u8{"./app"},
40+
});
41+
defer allocator.free(run_result.stdout);
42+
defer allocator.free(run_result.stderr);
43+
44+
// Clean up the app binary
45+
std.fs.cwd().deleteFile("./app") catch {};
46+
47+
switch (run_result.term) {
48+
.Exited => |code| {
49+
if (code != 0) {
50+
std.debug.print("Run failed with exit code {}\n", .{code});
51+
std.debug.print("STDOUT: {s}\n", .{run_result.stdout});
52+
std.debug.print("STDERR: {s}\n", .{run_result.stderr});
53+
return error.RunFailed;
54+
}
55+
},
56+
else => {
57+
std.debug.print("Run terminated abnormally: {}\n", .{run_result.term});
58+
std.debug.print("STDOUT: {s}\n", .{run_result.stdout});
59+
std.debug.print("STDERR: {s}\n", .{run_result.stderr});
60+
return error.RunFailed;
61+
},
62+
}
63+
64+
// Verify stdout contains expected messages
65+
try testing.expect(std.mem.indexOf(u8, run_result.stdout, "Hello from stdout!") != null);
66+
try testing.expect(std.mem.indexOf(u8, run_result.stdout, "Line 1 to stdout") != null);
67+
try testing.expect(std.mem.indexOf(u8, run_result.stdout, "Line 3 to stdout") != null);
68+
try testing.expect(std.mem.indexOf(u8, run_result.stdout, "ALL TESTS COMPLETED") != null);
69+
70+
// Verify stderr contains expected messages
71+
try testing.expect(std.mem.indexOf(u8, run_result.stderr, "Error from stderr!") != null);
72+
try testing.expect(std.mem.indexOf(u8, run_result.stderr, "Line 2 to stderr") != null);
73+
74+
// Verify stderr messages are NOT in stdout
75+
try testing.expect(std.mem.indexOf(u8, run_result.stdout, "Error from stderr!") == null);
76+
try testing.expect(std.mem.indexOf(u8, run_result.stdout, "Line 2 to stderr") == null);
77+
78+
// Verify stdout messages are NOT in stderr (except the ones we intentionally put there for display)
79+
// Note: The host.zig writes "STDOUT: ..." to stdout and "STDERR: ..." to stderr
80+
// so we check that the actual content is separated correctly
81+
}

test/fx/app.roc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
app [writeToStdout, writeToStderr] { pf: platform "./platform/main.roc" }
2+
3+
writeToStdout : Str => {}
4+
writeToStdout = |_msg|
5+
# This is an effectful function that will write to stdout
6+
# The host will actually handle the IO
7+
{}
8+
9+
writeToStderr : Str => {}
10+
writeToStderr = |_msg|
11+
# This is an effectful function that will write to stderr
12+
# The host will actually handle the IO
13+
{}

0 commit comments

Comments
 (0)