stdlib: relocate modules under library/
- examples/modules/ -> library/modules/ (top-level, no more symlink hacks in consumer projects) - compiler discovers stdlib via _NSGetExecutablePath / readlink /proc/self/exe; searches dev layout (../../library), install layout (../library), and alongside-binary fallback - SX_STDLIB_PATH env var overrides for tests / dev convenience - SX_DEBUG_STDLIB env var dumps the discovery results - build.zig installs library/ alongside the binary - Compilation gains stdlib_paths field threaded through resolveImports - 50 tests pass; consumer projects can now build from any cwd
This commit is contained in:
102
src/imports.zig
102
src/imports.zig
@@ -17,10 +17,13 @@ pub fn dirName(path: []const u8) []const u8 {
|
||||
return if (found) path[0..last_sep] else ".";
|
||||
}
|
||||
|
||||
/// Resolve an import path: try relative to base_dir first, fall back to cwd-relative.
|
||||
/// If root_path is provided, CWD-relative fallback paths are made absolute.
|
||||
/// Shared between compiler (resolveImports) and LSP (analyzeDocument).
|
||||
pub fn resolveImportPath(allocator: std.mem.Allocator, io: std.Io, base_dir: []const u8, raw_path: []const u8, root_path: ?[]const u8) ![]const u8 {
|
||||
/// Resolve an import path. Tries (in order):
|
||||
/// 1. relative to `base_dir` (the importing file's directory)
|
||||
/// 2. relative to CWD, absolutified via `root_path` if supplied
|
||||
/// 3. relative to each path in `stdlib_paths` (the install-discovered stdlib)
|
||||
/// Returns the first path that exists. Falls back to the raw path if nothing matches
|
||||
/// so the caller's readFile produces a coherent "not found" error.
|
||||
pub fn resolveImportPath(allocator: std.mem.Allocator, io: std.Io, base_dir: []const u8, raw_path: []const u8, root_path: ?[]const u8, stdlib_paths: []const []const u8) ![]const u8 {
|
||||
if (!std.mem.eql(u8, base_dir, ".")) {
|
||||
const rel_path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ base_dir, raw_path });
|
||||
// Check if it exists as file relative to base_dir
|
||||
@@ -33,13 +36,86 @@ pub fn resolveImportPath(allocator: std.mem.Allocator, io: std.Io, base_dir: []c
|
||||
return rel_path;
|
||||
} else |_| {}
|
||||
}
|
||||
// Fall back to raw path (cwd-relative); absolutify if root_path is known
|
||||
if (root_path) |rp| {
|
||||
// Try CWD-relative (absolutified if root_path is known).
|
||||
const cwd_candidate = if (root_path) |rp| blk: {
|
||||
if (rp.len > 0 and raw_path.len > 0 and raw_path[0] != '/') {
|
||||
return std.fmt.allocPrint(allocator, "{s}/{s}", .{ rp, raw_path });
|
||||
break :blk try std.fmt.allocPrint(allocator, "{s}/{s}", .{ rp, raw_path });
|
||||
}
|
||||
break :blk raw_path;
|
||||
} else raw_path;
|
||||
if (std.Io.Dir.readFileAlloc(.cwd(), io, cwd_candidate, allocator, .limited(10 * 1024 * 1024))) |_| {
|
||||
return cwd_candidate;
|
||||
} else |_| {}
|
||||
if (std.Io.Dir.openDir(.cwd(), io, cwd_candidate, .{})) |dir| {
|
||||
dir.close(io);
|
||||
return cwd_candidate;
|
||||
} else |_| {}
|
||||
// Try each stdlib search path.
|
||||
for (stdlib_paths) |sp| {
|
||||
const cand = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ sp, raw_path });
|
||||
if (std.Io.Dir.readFileAlloc(.cwd(), io, cand, allocator, .limited(10 * 1024 * 1024))) |_| {
|
||||
return cand;
|
||||
} else |_| {}
|
||||
if (std.Io.Dir.openDir(.cwd(), io, cand, .{})) |dir| {
|
||||
dir.close(io);
|
||||
return cand;
|
||||
} else |_| {}
|
||||
}
|
||||
return cwd_candidate;
|
||||
}
|
||||
|
||||
/// Discover candidate stdlib search paths from the running binary's location.
|
||||
/// Honors the `SX_STDLIB_PATH` env var as an explicit override. Returns a slice
|
||||
/// of absolute paths owned by the allocator.
|
||||
pub fn discoverStdlibPaths(allocator: std.mem.Allocator) ![]const []const u8 {
|
||||
var out = std.ArrayList([]const u8).empty;
|
||||
|
||||
// Env override via libc getenv (cross-stdlib-version stable).
|
||||
if (c_getenv("SX_STDLIB_PATH")) |env_path| {
|
||||
try out.append(allocator, try allocator.dupe(u8, std.mem.span(env_path)));
|
||||
}
|
||||
|
||||
const exe_path = selfExePath(allocator) catch return try out.toOwnedSlice(allocator);
|
||||
const exe_dir = dirName(exe_path);
|
||||
// Stdlib paths are directories containing a `modules/` subdir; the import
|
||||
// directive (e.g. `#import "modules/std.sx"`) supplies the rest.
|
||||
// Dev: zig-out/bin/sx -> repo-root/library
|
||||
try out.append(allocator, try std.fmt.allocPrint(allocator, "{s}/../../library", .{exe_dir}));
|
||||
// Install: <prefix>/bin/sx -> <prefix>/library
|
||||
try out.append(allocator, try std.fmt.allocPrint(allocator, "{s}/../library", .{exe_dir}));
|
||||
// Alongside the binary.
|
||||
try out.append(allocator, try std.fmt.allocPrint(allocator, "{s}/library", .{exe_dir}));
|
||||
if (c_getenv("SX_DEBUG_STDLIB") != null) {
|
||||
std.debug.print("[sx] exe_path={s}\n", .{exe_path});
|
||||
for (out.items, 0..) |p, i| std.debug.print("[sx] stdlib_paths[{d}]={s}\n", .{ i, p });
|
||||
}
|
||||
return try out.toOwnedSlice(allocator);
|
||||
}
|
||||
|
||||
const builtin = @import("builtin");
|
||||
|
||||
extern "c" fn _NSGetExecutablePath(buf: [*]u8, len: *u32) c_int;
|
||||
extern "c" fn getenv(name: [*:0]const u8) ?[*:0]const u8;
|
||||
|
||||
fn c_getenv(name: [:0]const u8) ?[*:0]const u8 {
|
||||
return getenv(name.ptr);
|
||||
}
|
||||
|
||||
fn selfExePath(allocator: std.mem.Allocator) ![]const u8 {
|
||||
var buf: [4096]u8 = undefined;
|
||||
switch (builtin.os.tag) {
|
||||
.macos, .ios => {
|
||||
var len: u32 = buf.len;
|
||||
if (_NSGetExecutablePath(&buf, &len) != 0) return error.PathBufferTooSmall;
|
||||
const span = std.mem.sliceTo(&buf, 0);
|
||||
return try allocator.dupe(u8, span);
|
||||
},
|
||||
.linux => {
|
||||
const n = try std.posix.readlink("/proc/self/exe", &buf);
|
||||
return try allocator.dupe(u8, n);
|
||||
},
|
||||
else => return error.UnsupportedHostOS,
|
||||
}
|
||||
return raw_path;
|
||||
}
|
||||
|
||||
/// A resolved module: the fully-resolved declarations of a single .sx file,
|
||||
@@ -98,6 +174,7 @@ pub fn resolveImports(
|
||||
cache: *ModuleCache,
|
||||
source_map: ?*std.StringHashMap([:0]const u8),
|
||||
diagnostics: ?*errors.DiagnosticList,
|
||||
stdlib_paths: []const []const u8,
|
||||
) !ResolvedModule {
|
||||
var mod = ResolvedModule{
|
||||
.path = file_path,
|
||||
@@ -167,7 +244,7 @@ pub fn resolveImports(
|
||||
}
|
||||
const imp = decl.data.import_decl;
|
||||
|
||||
const resolved_path = try resolveImportPath(allocator, io, base_dir, imp.path, null);
|
||||
const resolved_path = try resolveImportPath(allocator, io, base_dir, imp.path, null, stdlib_paths);
|
||||
|
||||
// Circular import check — only along the current chain
|
||||
if (chain.contains(resolved_path)) continue;
|
||||
@@ -195,7 +272,7 @@ pub fn resolveImports(
|
||||
// Push onto chain before recursing, pop after
|
||||
try chain.put(resolved_path, {});
|
||||
const imp_dir = dirName(resolved_path);
|
||||
const result = try resolveImports(allocator, io, imp_root, imp_dir, resolved_path, chain, cache, source_map, diagnostics);
|
||||
const result = try resolveImports(allocator, io, imp_root, imp_dir, resolved_path, chain, cache, source_map, diagnostics, stdlib_paths);
|
||||
_ = chain.remove(resolved_path);
|
||||
|
||||
// Cache
|
||||
@@ -203,7 +280,7 @@ pub fn resolveImports(
|
||||
break :blk result;
|
||||
} else |_| {
|
||||
// File read failed — try as directory import
|
||||
const result = resolveDirectoryImport(allocator, io, resolved_path, chain, cache, source_map, diagnostics, decl.span) catch {
|
||||
const result = resolveDirectoryImport(allocator, io, resolved_path, chain, cache, source_map, diagnostics, decl.span, stdlib_paths) catch {
|
||||
if (diagnostics) |diags| {
|
||||
diags.addFmt(.err, decl.span, "cannot read import '{s}' (not a file or directory)", .{resolved_path});
|
||||
}
|
||||
@@ -235,6 +312,7 @@ fn resolveDirectoryImport(
|
||||
source_map: ?*std.StringHashMap([:0]const u8),
|
||||
diagnostics: ?*errors.DiagnosticList,
|
||||
span: ast.Span,
|
||||
stdlib_paths: []const []const u8,
|
||||
) anyerror!ResolvedModule {
|
||||
// Open the directory with iteration capability
|
||||
const dir = std.Io.Dir.openDir(.cwd(), io, dir_path, .{ .iterate = true }) catch {
|
||||
@@ -300,7 +378,7 @@ fn resolveDirectoryImport(
|
||||
};
|
||||
|
||||
try chain.put(file_path, {});
|
||||
const result = try resolveImports(allocator, io, imp_root, dir_path, file_path, chain, cache, source_map, diagnostics);
|
||||
const result = try resolveImports(allocator, io, imp_root, dir_path, file_path, chain, cache, source_map, diagnostics, stdlib_paths);
|
||||
_ = chain.remove(file_path);
|
||||
|
||||
try cache.put(file_path, result);
|
||||
|
||||
Reference in New Issue
Block a user