start re-organizing, leave a plan for future PR's

This commit is contained in:
Luke Boswell 2025-12-17 14:33:52 +11:00
parent 6a1086e0a9
commit d0b451fb78
No known key found for this signature in database
GPG key ID: 54A7324B1B975757
9 changed files with 152 additions and 127 deletions

View file

@ -37,7 +37,7 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const reporting = @import("reporting");
const problem_mod = @import("problem.zig");
const problem_mod = @import("CliProblem.zig");
const CliProblem = problem_mod.CliProblem;
const Report = reporting.Report;

90
src/cli/REORGANIZATION.md Normal file
View file

@ -0,0 +1,90 @@
# CLI Directory Reorganization Plan
This document outlines future work to reorganize the CLI module for better maintainability and idiomatic Zig conventions.
## Design Principles
1. **Flat structure** - No subdirectories, use descriptive filenames to group related code
2. **TitleCase.zig** for files defining a single primary type (e.g., `CliContext.zig`)
3. **snake_case.zig** for namespace/function modules (e.g., `cli_args.zig`)
4. **cli_roc_* prefix** for command implementation files to avoid conflicts
5. Direct imports rather than re-export hubs
## Current State
The CLI module is functional but `main.zig` is ~5,500 lines containing all command implementations.
## Future Structure
```
src/cli/
├── main.zig # Slim entrypoint (~300 lines): dispatch only
├── CliContext.zig # Main CLI context type (DONE)
├── CliProblem.zig # Runtime error types (DONE)
├── cli_args.zig # Argument parsing (ArgProblem renamed, DONE)
├── cli_roc_run.zig # rocRun command implementation
├── cli_roc_build.zig # rocBuild command implementation
├── cli_roc_check.zig # rocCheck command implementation
├── cli_roc_test.zig # rocTest command implementation
├── cli_roc_format.zig # rocFormat command implementation
├── cli_roc_docs.zig # rocDocs command implementation
├── cli_roc_bundle.zig # rocBundle command implementation
├── cli_roc_unbundle.zig # rocUnbundle command implementation
├── platform_resolution.zig # Platform spec parsing, path resolution
├── platform_cache.zig # getRocCacheDir, URL platform resolution
├── platform_validation.zig # Platform header validation (existing)
├── compile_serialization.zig # compileAndSerializeModulesForEmbedding
├── compile_shared_memory.zig # POSIX/Windows shared memory
├── builder.zig # LLVM bitcode compilation (existing)
├── linker.zig # LLD wrapper (existing)
├── platform_host_shim.zig # LLVM shim generation (existing)
├── target.zig # Target definitions (existing)
├── targets_validator.zig # Targets section validation (existing)
├── cross_compilation.zig # Cross-compilation validation (existing)
├── libc_finder.zig # Linux libc detection (existing)
├── util_windows.zig # Windows console functions
├── util_posix.zig # POSIX shm wrappers
├── util_timing.zig # formatElapsedTime
├── bench.zig # Benchmarking utility (existing)
├── targets/ # Pre-compiled shim libraries (keep)
└── test/ # Test files (keep)
```
## Migration Phases
### Phase 1: Extract Utilities from main.zig
1. Create `util_windows.zig` - Windows console functions
2. Create `util_posix.zig` - POSIX shm wrappers
3. Create `util_timing.zig` - formatElapsedTime, printTimingBreakdown
### Phase 2: Extract Compilation Infrastructure
1. Create `compile_shared_memory.zig` - SharedMemoryHandle, write functions
2. Create `compile_serialization.zig` - compileAndSerializeModulesForEmbedding
### Phase 3: Extract Platform Resolution
1. Create `platform_resolution.zig` - extractPlatformSpecFromApp, resolvePlatformPaths
2. Create `platform_cache.zig` - getRocCacheDir, resolveUrlPlatform
### Phase 4: Extract Commands (Ordered by Complexity)
1. `cli_roc_format.zig` (simplest)
2. `cli_roc_unbundle.zig`
3. `cli_roc_bundle.zig`
4. `cli_roc_test.zig`
5. `cli_roc_check.zig`
6. `cli_roc_docs.zig`
7. `cli_roc_build.zig`
8. `cli_roc_run.zig` (most complex)
### Phase 5: Slim main.zig
Reduce main.zig to ~300 lines containing only:
- Entry point
- Command dispatch
- Top-level error handling

View file

@ -21,7 +21,7 @@ pub const CliArgs = union(enum) {
experimental_lsp: ExperimentalLspArgs,
help: []const u8,
licenses,
problem: CliProblem,
problem: ArgProblem,
pub fn deinit(self: CliArgs, alloc: mem.Allocator) void {
switch (self) {
@ -35,7 +35,7 @@ pub const CliArgs = union(enum) {
};
/// Errors that can occur due to bad input while parsing the arguments
pub const CliProblem = union(enum) {
pub const ArgProblem = union(enum) {
missing_flag_value: struct {
flag: []const u8,
},
@ -241,7 +241,7 @@ fn parseCheck(args: []const []const u8) CliArgs {
if (getFlagValue(arg)) |value| {
main = value;
} else {
return CliArgs{ .problem = CliProblem{ .missing_flag_value = .{ .flag = "--main" } } };
return CliArgs{ .problem = ArgProblem{ .missing_flag_value = .{ .flag = "--main" } } };
}
} else if (mem.eql(u8, arg, "--time")) {
time = true;
@ -251,7 +251,7 @@ fn parseCheck(args: []const []const u8) CliArgs {
verbose = true;
} else {
if (path != null) {
return CliArgs{ .problem = CliProblem{ .unexpected_argument = .{ .cmd = "check", .arg = arg } } };
return CliArgs{ .problem = ArgProblem{ .unexpected_argument = .{ .cmd = "check", .arg = arg } } };
}
path = arg;
}
@ -298,35 +298,35 @@ fn parseBuild(args: []const []const u8) CliArgs {
if (getFlagValue(arg)) |value| {
target = value;
} else {
return CliArgs{ .problem = CliProblem{ .missing_flag_value = .{ .flag = "--target" } } };
return CliArgs{ .problem = ArgProblem{ .missing_flag_value = .{ .flag = "--target" } } };
}
} else if (mem.startsWith(u8, arg, "--output")) {
if (getFlagValue(arg)) |value| {
output = value;
} else {
return CliArgs{ .problem = CliProblem{ .missing_flag_value = .{ .flag = "--output" } } };
return CliArgs{ .problem = ArgProblem{ .missing_flag_value = .{ .flag = "--output" } } };
}
} else if (mem.startsWith(u8, arg, "--opt")) {
if (getFlagValue(arg)) |value| {
if (OptLevel.from_str(value)) |level| {
opt = level;
} else {
return CliArgs{ .problem = CliProblem{ .invalid_flag_value = .{ .flag = "--opt", .value = value, .valid_options = "speed,size,dev" } } };
return CliArgs{ .problem = ArgProblem{ .invalid_flag_value = .{ .flag = "--opt", .value = value, .valid_options = "speed,size,dev" } } };
}
} else {
return CliArgs{ .problem = CliProblem{ .missing_flag_value = .{ .flag = "--opt" } } };
return CliArgs{ .problem = ArgProblem{ .missing_flag_value = .{ .flag = "--opt" } } };
}
} else if (mem.startsWith(u8, arg, "--z-bench-tokenize")) {
if (getFlagValue(arg)) |value| {
z_bench_tokenize = value;
} else {
return CliArgs{ .problem = CliProblem{ .missing_flag_value = .{ .flag = "--z-bench-tokenize" } } };
return CliArgs{ .problem = ArgProblem{ .missing_flag_value = .{ .flag = "--z-bench-tokenize" } } };
}
} else if (mem.startsWith(u8, arg, "--z-bench-parse")) {
if (getFlagValue(arg)) |value| {
z_bench_parse = value;
} else {
return CliArgs{ .problem = CliProblem{ .missing_flag_value = .{ .flag = "--z-bench-parse" } } };
return CliArgs{ .problem = ArgProblem{ .missing_flag_value = .{ .flag = "--z-bench-parse" } } };
}
} else if (mem.eql(u8, arg, "--debug")) {
debug = true;
@ -335,22 +335,22 @@ fn parseBuild(args: []const []const u8) CliArgs {
} else if (mem.startsWith(u8, arg, "--wasm-memory")) {
if (getFlagValue(arg)) |value| {
wasm_memory = std.fmt.parseInt(usize, value, 10) catch {
return CliArgs{ .problem = CliProblem{ .invalid_flag_value = .{ .flag = "--wasm-memory", .value = value, .valid_options = "positive integer (bytes)" } } };
return CliArgs{ .problem = ArgProblem{ .invalid_flag_value = .{ .flag = "--wasm-memory", .value = value, .valid_options = "positive integer (bytes)" } } };
};
} else {
return CliArgs{ .problem = CliProblem{ .missing_flag_value = .{ .flag = "--wasm-memory" } } };
return CliArgs{ .problem = ArgProblem{ .missing_flag_value = .{ .flag = "--wasm-memory" } } };
}
} else if (mem.startsWith(u8, arg, "--wasm-stack-size")) {
if (getFlagValue(arg)) |value| {
wasm_stack_size = std.fmt.parseInt(usize, value, 10) catch {
return CliArgs{ .problem = CliProblem{ .invalid_flag_value = .{ .flag = "--wasm-stack-size", .value = value, .valid_options = "positive integer (bytes)" } } };
return CliArgs{ .problem = ArgProblem{ .invalid_flag_value = .{ .flag = "--wasm-stack-size", .value = value, .valid_options = "positive integer (bytes)" } } };
};
} else {
return CliArgs{ .problem = CliProblem{ .missing_flag_value = .{ .flag = "--wasm-stack-size" } } };
return CliArgs{ .problem = ArgProblem{ .missing_flag_value = .{ .flag = "--wasm-stack-size" } } };
}
} else {
if (path != null) {
return CliArgs{ .problem = CliProblem{ .unexpected_argument = .{ .cmd = "build", .arg = arg } } };
return CliArgs{ .problem = ArgProblem{ .unexpected_argument = .{ .cmd = "build", .arg = arg } } };
}
path = arg;
}
@ -385,27 +385,27 @@ fn parseBundle(alloc: mem.Allocator, args: []const []const u8) std.mem.Allocator
} else if (mem.eql(u8, arg, "--output-dir")) {
if (i + 1 >= args.len) {
paths.deinit();
return CliArgs{ .problem = CliProblem{ .missing_flag_value = .{ .flag = "--output-dir" } } };
return CliArgs{ .problem = ArgProblem{ .missing_flag_value = .{ .flag = "--output-dir" } } };
}
i += 1;
output_dir = args[i];
} else if (mem.eql(u8, arg, "--compression")) {
if (i + 1 >= args.len) {
paths.deinit();
return CliArgs{ .problem = CliProblem{ .missing_flag_value = .{ .flag = "--compression" } } };
return CliArgs{ .problem = ArgProblem{ .missing_flag_value = .{ .flag = "--compression" } } };
}
i += 1;
compression_level = std.fmt.parseInt(i32, args[i], 10) catch {
paths.deinit();
return CliArgs{ .problem = CliProblem{ .invalid_flag_value = .{ .value = args[i], .flag = "--compression", .valid_options = "integer between 1 and 22" } } };
return CliArgs{ .problem = ArgProblem{ .invalid_flag_value = .{ .value = args[i], .flag = "--compression", .valid_options = "integer between 1 and 22" } } };
};
if (compression_level < 1 or compression_level > 22) {
paths.deinit();
return CliArgs{ .problem = CliProblem{ .invalid_flag_value = .{ .value = args[i], .flag = "--compression", .valid_options = "integer between 1 and 22" } } };
return CliArgs{ .problem = ArgProblem{ .invalid_flag_value = .{ .value = args[i], .flag = "--compression", .valid_options = "integer between 1 and 22" } } };
}
} else if (mem.startsWith(u8, arg, "--")) {
paths.deinit();
return CliArgs{ .problem = CliProblem{ .unexpected_argument = .{ .cmd = "bundle", .arg = arg } } };
return CliArgs{ .problem = ArgProblem{ .unexpected_argument = .{ .cmd = "bundle", .arg = arg } } };
} else {
try paths.append(arg);
}
@ -444,7 +444,7 @@ fn parseUnbundle(alloc: mem.Allocator, args: []const []const u8) !CliArgs {
};
} else if (mem.startsWith(u8, arg, "-")) {
paths.deinit();
return CliArgs{ .problem = CliProblem{ .unexpected_argument = .{ .cmd = "unbundle", .arg = arg } } };
return CliArgs{ .problem = ArgProblem{ .unexpected_argument = .{ .cmd = "unbundle", .arg = arg } } };
} else {
try paths.append(arg);
}
@ -552,23 +552,23 @@ fn parseTest(args: []const []const u8) CliArgs {
if (getFlagValue(arg)) |value| {
main = value;
} else {
return CliArgs{ .problem = CliProblem{ .missing_flag_value = .{ .flag = "--main" } } };
return CliArgs{ .problem = ArgProblem{ .missing_flag_value = .{ .flag = "--main" } } };
}
} else if (mem.startsWith(u8, arg, "--opt")) {
if (getFlagValue(arg)) |value| {
if (OptLevel.from_str(value)) |level| {
opt = level;
} else {
return CliArgs{ .problem = CliProblem{ .invalid_flag_value = .{ .flag = "--opt", .value = value, .valid_options = "speed,size,dev" } } };
return CliArgs{ .problem = ArgProblem{ .invalid_flag_value = .{ .flag = "--opt", .value = value, .valid_options = "speed,size,dev" } } };
}
} else {
return CliArgs{ .problem = CliProblem{ .missing_flag_value = .{ .flag = "--opt" } } };
return CliArgs{ .problem = ArgProblem{ .missing_flag_value = .{ .flag = "--opt" } } };
}
} else if (mem.eql(u8, arg, "--verbose")) {
verbose = true;
} else {
if (path != null) {
return CliArgs{ .problem = CliProblem{ .unexpected_argument = .{ .cmd = "test", .arg = arg } } };
return CliArgs{ .problem = ArgProblem{ .unexpected_argument = .{ .cmd = "test", .arg = arg } } };
}
path = arg;
}
@ -589,7 +589,7 @@ fn parseRepl(args: []const []const u8) CliArgs {
\\
};
} else {
return CliArgs{ .problem = CliProblem{ .unexpected_argument = .{ .cmd = "repl", .arg = arg } } };
return CliArgs{ .problem = ArgProblem{ .unexpected_argument = .{ .cmd = "repl", .arg = arg } } };
}
}
return CliArgs.repl;
@ -608,7 +608,7 @@ fn parseVersion(args: []const []const u8) CliArgs {
\\
};
} else {
return CliArgs{ .problem = CliProblem{ .unexpected_argument = .{ .cmd = "version", .arg = arg } } };
return CliArgs{ .problem = ArgProblem{ .unexpected_argument = .{ .cmd = "version", .arg = arg } } };
}
}
return CliArgs.version;
@ -627,7 +627,7 @@ fn parseLicenses(args: []const []const u8) CliArgs {
\\
};
} else {
return CliArgs{ .problem = CliProblem{ .unexpected_argument = .{ .cmd = "licenses", .arg = arg } } };
return CliArgs{ .problem = ArgProblem{ .unexpected_argument = .{ .cmd = "licenses", .arg = arg } } };
}
}
return CliArgs.licenses;
@ -666,13 +666,13 @@ fn parseDocs(args: []const []const u8) CliArgs {
if (getFlagValue(arg)) |value| {
main = value;
} else {
return CliArgs{ .problem = CliProblem{ .missing_flag_value = .{ .flag = "--main" } } };
return CliArgs{ .problem = ArgProblem{ .missing_flag_value = .{ .flag = "--main" } } };
}
} else if (mem.startsWith(u8, arg, "--output")) {
if (getFlagValue(arg)) |value| {
output = value;
} else {
return CliArgs{ .problem = CliProblem{ .missing_flag_value = .{ .flag = "--output" } } };
return CliArgs{ .problem = ArgProblem{ .missing_flag_value = .{ .flag = "--output" } } };
}
} else if (mem.eql(u8, arg, "--serve")) {
serve = true;
@ -684,7 +684,7 @@ fn parseDocs(args: []const []const u8) CliArgs {
verbose = true;
} else {
if (path != null) {
return CliArgs{ .problem = CliProblem{ .unexpected_argument = .{ .cmd = "docs", .arg = arg } } };
return CliArgs{ .problem = ArgProblem{ .unexpected_argument = .{ .cmd = "docs", .arg = arg } } };
}
path = arg;
}
@ -723,7 +723,7 @@ fn parseExperimentalLsp(args: []const []const u8) CliArgs {
} else if (mem.eql(u8, arg, "--debug-server")) {
debug_server = true;
} else {
return CliArgs{ .problem = CliProblem{ .unexpected_argument = .{ .cmd = "experimental-lsp", .arg = arg } } };
return CliArgs{ .problem = ArgProblem{ .unexpected_argument = .{ .cmd = "experimental-lsp", .arg = arg } } };
}
}
@ -770,7 +770,7 @@ fn parseRun(alloc: mem.Allocator, args: []const []const u8) std.mem.Allocator.Er
target = value;
} else {
app_args.deinit();
return CliArgs{ .problem = CliProblem{ .missing_flag_value = .{ .flag = "--target" } } };
return CliArgs{ .problem = ArgProblem{ .missing_flag_value = .{ .flag = "--target" } } };
}
} else if (mem.startsWith(u8, arg, "--opt")) {
if (getFlagValue(arg)) |value| {
@ -778,11 +778,11 @@ fn parseRun(alloc: mem.Allocator, args: []const []const u8) std.mem.Allocator.Er
opt = level;
} else {
app_args.deinit();
return CliArgs{ .problem = CliProblem{ .invalid_flag_value = .{ .flag = "--opt", .value = value, .valid_options = "speed,size,dev" } } };
return CliArgs{ .problem = ArgProblem{ .invalid_flag_value = .{ .flag = "--opt", .value = value, .valid_options = "speed,size,dev" } } };
}
} else {
app_args.deinit();
return CliArgs{ .problem = CliProblem{ .missing_flag_value = .{ .flag = "--opt" } } };
return CliArgs{ .problem = ArgProblem{ .missing_flag_value = .{ .flag = "--opt" } } };
}
} else if (mem.eql(u8, arg, "--no-cache")) {
no_cache = true;

View file

@ -1,70 +0,0 @@
//! CLI Context and Error Handling
//!
//! Provides the shared CLI context and structured error types for CLI operations.
//!
//! The key design principle is that `CliError` is the ONLY error type that
//! CLI functions should return. This ensures:
//! - Every error is properly reported (no silent failures)
//! - Consistent error formatting across all commands
//! - The type system enforces proper error handling
//!
//! This module exports:
//! - `CliContext`: Shared context with allocators, I/O, and error accumulation
//! - `Io`: I/O interface wrapping stdout/stderr with buffered writers
//! - `CliError`: The single error type for CLI operations
//! - `CliProblem`: Union type representing all CLI error conditions
//! - `FileContext`: Context enum for file operation errors
//! - `Command`: CLI command enum
//!
//! Usage:
//! ```zig
//! const cli = @import("cli_error.zig");
//!
//! fn doSomething(ctx: *cli.CliContext, path: []const u8) cli.CliError!void {
//! const source = std.fs.cwd().readFileAlloc(ctx.gpa, path, ...) catch {
//! return ctx.fail(.{ .file_not_found = .{ .path = path } });
//! };
//! defer ctx.gpa.free(source);
//! // Use ctx.arena for temporary allocations...
//! }
//!
//! // At top level:
//! var io = cli.Io.init();
//! var ctx = cli.CliContext.init(gpa, arena, &io, .build);
//! ctx.initIo(); // Initialize I/O writers after ctx is at its final location
//! defer ctx.deinit();
//!
//! doSomething(&ctx, "app.roc") catch |err| switch (err) {
//! error.CliError => {}, // Problems already recorded
//! };
//!
//! try ctx.renderProblemsTo(ctx.io.stderr());
//! return ctx.exitCode();
//! ```
const problem = @import("cli_error/problem.zig");
const context = @import("cli_error/context.zig");
// Re-export error type
pub const CliError = context.CliError;
// Re-export main types - CliContext is the primary type
pub const CliContext = context.CliContext;
pub const Io = context.Io;
pub const CliProblem = problem.CliProblem;
pub const FileContext = problem.FileContext;
pub const Command = context.Command;
// Backward compatibility alias
pub const CliErrorContext = context.CliErrorContext;
// Re-export helper functions
pub const reportSingleProblem = context.reportSingleProblem;
pub const renderProblem = context.renderProblem;
const std = @import("std");
test "cli_error tests" {
std.testing.refAllDecls(@import("cli_error/problem.zig"));
std.testing.refAllDecls(@import("cli_error/context.zig"));
}

View file

@ -10,7 +10,7 @@ const std = @import("std");
const builtin = @import("builtin");
const base = @import("base");
const Allocators = base.Allocators;
const cli_ctx = @import("cli_error/context.zig");
const cli_ctx = @import("CliContext.zig");
const CliContext = cli_ctx.CliContext;
const Io = cli_ctx.Io;
const fs = std.fs;

View file

@ -10,7 +10,7 @@ const base = @import("base");
const Allocators = base.Allocators;
const libc_finder = @import("libc_finder.zig");
const RocTarget = @import("roc_target").RocTarget;
const cli_ctx = @import("cli_error/context.zig");
const cli_ctx = @import("CliContext.zig");
const CliContext = cli_ctx.CliContext;
const Io = cli_ctx.Io;

View file

@ -54,18 +54,23 @@ const cli_args = @import("cli_args.zig");
const roc_target = @import("target.zig");
pub const targets_validator = @import("targets_validator.zig");
const platform_validation = @import("platform_validation.zig");
const cli_error = @import("cli_error.zig");
const cli_context = @import("CliContext.zig");
const cli_problem = @import("CliProblem.zig");
const CliProblem = cli_error.CliProblem;
const CliContext = cli_error.CliContext;
const Io = cli_error.Io;
const CliProblem = cli_problem.CliProblem;
const CliContext = cli_context.CliContext;
const Io = cli_context.Io;
const Command = cli_context.Command;
const CliError = cli_context.CliError;
const renderProblem = cli_context.renderProblem;
comptime {
if (builtin.is_test) {
std.testing.refAllDecls(cli_args);
std.testing.refAllDecls(targets_validator);
std.testing.refAllDecls(platform_validation);
std.testing.refAllDecls(cli_error);
std.testing.refAllDecls(cli_context);
std.testing.refAllDecls(cli_problem);
}
}
const bench = @import("bench.zig");
@ -679,7 +684,7 @@ fn mainArgs(allocs: *Allocators, args: []const []const u8) !void {
const parsed_args = try cli_args.parse(allocs.arena, args[1..]);
// Determine command for context
const command: cli_error.Command = switch (parsed_args) {
const command: Command = switch (parsed_args) {
.run => .run,
.build => .build,
.check => .check,
@ -1261,7 +1266,7 @@ fn appendWindowsQuotedArg(cmd_builder: *std.array_list.Managed(u8), arg: []const
}
/// Run child process using Windows handle inheritance (idiomatic Windows approach)
fn runWithWindowsHandleInheritance(ctx: *CliContext, exe_path: []const u8, shm_handle: SharedMemoryHandle, app_args: []const []const u8) (cli_error.CliError || error{OutOfMemory})!void {
fn runWithWindowsHandleInheritance(ctx: *CliContext, exe_path: []const u8, shm_handle: SharedMemoryHandle, app_args: []const []const u8) (CliError || error{OutOfMemory})!void {
// Make the shared memory handle inheritable
if (windows.SetHandleInformation(@ptrCast(shm_handle.fd), windows.HANDLE_FLAG_INHERIT, windows.HANDLE_FLAG_INHERIT) == 0) {
return ctx.fail(.{ .shared_memory_failed = .{
@ -1377,7 +1382,7 @@ fn runWithWindowsHandleInheritance(ctx: *CliContext, exe_path: []const u8, shm_h
/// Run child process using POSIX file descriptor inheritance (existing approach for Unix)
/// The exe_path should already be in a unique temp directory created by createUniqueTempDir.
fn runWithPosixFdInheritance(ctx: *CliContext, exe_path: []const u8, shm_handle: SharedMemoryHandle, app_args: []const []const u8) (cli_error.CliError || error{OutOfMemory})!void {
fn runWithPosixFdInheritance(ctx: *CliContext, exe_path: []const u8, shm_handle: SharedMemoryHandle, app_args: []const []const u8) (CliError || error{OutOfMemory})!void {
// Write the coordination file (.txt) next to the executable
// The executable is already in a unique temp directory
std.log.debug("Writing fd coordination file for: {s}", .{exe_path});
@ -1793,7 +1798,7 @@ pub fn setupSharedMemoryWithModuleEnv(ctx: *CliContext, roc_file_path: []const u
.err = err,
} },
};
cli_error.renderProblem(ctx.gpa, ctx.io.stderr(), problem);
renderProblem(ctx.gpa, ctx.io.stderr(), problem);
return error.FileNotFound;
};
defer app_file.close();
@ -2808,7 +2813,7 @@ pub const PlatformPaths = struct {
/// Resolve platform specification from a Roc file to find both host library and platform source.
/// Returns PlatformPaths with arena-allocated paths (no need to free).
pub fn resolvePlatformPaths(ctx: *CliContext, roc_file_path: []const u8) cli_error.CliError!PlatformPaths {
pub fn resolvePlatformPaths(ctx: *CliContext, roc_file_path: []const u8) CliError!PlatformPaths {
// Use the parser to extract the platform spec
const platform_spec = extractPlatformSpecFromApp(ctx, roc_file_path) catch {
return ctx.fail(.{ .file_not_found = .{
@ -2918,7 +2923,7 @@ fn stringFromExpr(ast: *parse.AST, expr_idx: parse.AST.Expr.Idx) ![]const u8 {
/// Check if platform spec is an absolute path and reject it.
/// Uses CliContext for error reporting.
fn validatePlatformSpec(ctx: *CliContext, platform_spec: []const u8) cli_error.CliError!void {
fn validatePlatformSpec(ctx: *CliContext, platform_spec: []const u8) CliError!void {
if (std.fs.path.isAbsolute(platform_spec)) {
return ctx.fail(.{ .absolute_platform_path = .{ .platform_spec = platform_spec } });
}
@ -2926,7 +2931,7 @@ fn validatePlatformSpec(ctx: *CliContext, platform_spec: []const u8) cli_error.C
/// Resolve a platform specification to a platform source path.
/// Uses CliContext for error reporting.
fn resolvePlatformSpecToPaths(ctx: *CliContext, platform_spec: []const u8, base_dir: []const u8) cli_error.CliError!PlatformPaths {
fn resolvePlatformSpecToPaths(ctx: *CliContext, platform_spec: []const u8, base_dir: []const u8) CliError!PlatformPaths {
// Handle URL-based platforms
if (std.mem.startsWith(u8, platform_spec, "http")) {
return resolveUrlPlatform(ctx, platform_spec) catch |err| switch (err) {
@ -3009,7 +3014,7 @@ fn getEnvVar(allocator: std.mem.Allocator, key: []const u8) ?[]const u8 {
/// Resolve a URL platform specification by downloading and caching the bundle.
/// The URL must point to a .tar.zst bundle with a base58-encoded BLAKE3 hash filename.
fn resolveUrlPlatform(ctx: *CliContext, url: []const u8) (cli_error.CliError || error{OutOfMemory})!PlatformPaths {
fn resolveUrlPlatform(ctx: *CliContext, url: []const u8) (CliError || error{OutOfMemory})!PlatformPaths {
const download = unbundle.download;
// 1. Validate URL and extract hash
@ -3622,7 +3627,7 @@ fn rocBuildEmbedded(ctx: *CliContext, args: cli_args.BuildArgs) !void {
return error.MissingTargetsSection;
},
else => {
cli_error.renderProblem(ctx.gpa, ctx.io.stderr(), .{
renderProblem(ctx.gpa, ctx.io.stderr(), .{
.platform_validation_failed = .{
.message = "Failed to validate platform header",
},
@ -3632,7 +3637,7 @@ fn rocBuildEmbedded(ctx: *CliContext, args: cli_args.BuildArgs) !void {
}
}
else {
cli_error.renderProblem(ctx.gpa, ctx.io.stderr(), .{
renderProblem(ctx.gpa, ctx.io.stderr(), .{
.no_platform_found = .{ .app_path = args.path },
});
return error.NoPlatformSource;
@ -3674,7 +3679,7 @@ fn rocBuildEmbedded(ctx: *CliContext, args: cli_args.BuildArgs) !void {
} else blk: {
// No --target provided: find the first compatible target across all link types
const compatible = targets_config.getFirstCompatibleTarget() orelse {
cli_error.renderProblem(ctx.gpa, ctx.io.stderr(), .{
renderProblem(ctx.gpa, ctx.io.stderr(), .{
.platform_validation_failed = .{
.message = "No compatible target found. The platform does not support any target compatible with this system.",
},

View file

@ -6,9 +6,9 @@ const testing = std.testing;
const main = @import("main.zig");
const base = @import("base");
const Allocators = base.Allocators;
const cli_error = @import("cli_error.zig");
const CliContext = cli_error.CliContext;
const Io = cli_error.Io;
const cli_context = @import("CliContext.zig");
const CliContext = cli_context.CliContext;
const Io = cli_context.Io;
test "platform resolution - basic cli platform" {
var gpa_impl = std.heap.GeneralPurposeAllocator(.{}){};