mirror of
https://github.com/roc-lang/roc.git
synced 2025-12-23 08:48:03 +00:00
265 lines
9.7 KiB
Zig
265 lines
9.7 KiB
Zig
//! Roc target definitions and link configuration
|
|
//!
|
|
//! Re-exports RocTarget and adds link configuration types that depend on the parse module.
|
|
|
|
const std = @import("std");
|
|
const parse = @import("parse");
|
|
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
// Re-export RocTarget from the shared build module
|
|
pub const RocTarget = @import("roc_target").RocTarget;
|
|
|
|
/// Individual link item from a targets section
|
|
/// Can be a file path (relative to files/ directory) or a special identifier
|
|
pub const LinkItem = union(enum) {
|
|
/// A file path (string literal in the source)
|
|
/// Path is relative to the targets/<target>/ directory
|
|
file_path: []const u8,
|
|
|
|
/// The compiled Roc application
|
|
app,
|
|
|
|
/// Windows GUI subsystem flag (/subsystem:windows)
|
|
win_gui,
|
|
};
|
|
|
|
/// Link specification for a single target
|
|
/// Contains the ordered list of items to link for this target
|
|
pub const TargetLinkSpec = struct {
|
|
target: RocTarget,
|
|
items: []const LinkItem,
|
|
};
|
|
|
|
/// Type of output binary
|
|
pub const LinkType = enum {
|
|
/// Executable binary
|
|
exe,
|
|
/// Static library (.a, .lib)
|
|
static_lib,
|
|
/// Shared/dynamic library (.so, .dylib, .dll)
|
|
shared_lib,
|
|
};
|
|
|
|
/// Complete targets configuration from a platform header
|
|
pub const TargetsConfig = struct {
|
|
/// Base directory for target-specific files (e.g., "targets/")
|
|
files_dir: ?[]const u8,
|
|
|
|
/// Executable target specifications (in priority order)
|
|
exe: []const TargetLinkSpec,
|
|
|
|
/// Static library target specifications (in priority order)
|
|
static_lib: []const TargetLinkSpec,
|
|
|
|
/// Shared library target specifications (in priority order)
|
|
shared_lib: []const TargetLinkSpec,
|
|
|
|
/// Get the link spec for a specific target and link type
|
|
pub fn getLinkSpec(self: TargetsConfig, target: RocTarget, link_type: LinkType) ?TargetLinkSpec {
|
|
const specs = switch (link_type) {
|
|
.exe => self.exe,
|
|
.static_lib => self.static_lib,
|
|
.shared_lib => self.shared_lib,
|
|
};
|
|
for (specs) |spec| {
|
|
if (spec.target == target) {
|
|
return spec;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Get the default target for a given link type based on the current system
|
|
/// Returns the first target in the list that's compatible with the current host (OS and arch)
|
|
pub fn getDefaultTarget(self: TargetsConfig, link_type: LinkType) ?RocTarget {
|
|
const specs = switch (link_type) {
|
|
.exe => self.exe,
|
|
.static_lib => self.static_lib,
|
|
.shared_lib => self.shared_lib,
|
|
};
|
|
|
|
// First pass: look for exact OS and architecture match
|
|
for (specs) |spec| {
|
|
if (spec.target.isCompatibleWithHost()) {
|
|
return spec.target;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// Result of finding a compatible target
|
|
pub const CompatibleTarget = struct {
|
|
target: RocTarget,
|
|
link_type: LinkType,
|
|
};
|
|
|
|
/// Get the first compatible target across all link types.
|
|
/// Iterates through exe, static_lib, shared_lib in order,
|
|
/// returning the first target compatible with the current host.
|
|
pub fn getFirstCompatibleTarget(self: TargetsConfig) ?CompatibleTarget {
|
|
const link_types = [_]LinkType{ .exe, .static_lib, .shared_lib };
|
|
|
|
for (link_types) |lt| {
|
|
const specs = self.getSupportedTargets(lt);
|
|
for (specs) |spec| {
|
|
if (spec.target.isCompatibleWithHost()) {
|
|
return CompatibleTarget{ .target = spec.target, .link_type = lt };
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// Check if a specific target is supported
|
|
pub fn supportsTarget(self: TargetsConfig, target: RocTarget, link_type: LinkType) bool {
|
|
return self.getLinkSpec(target, link_type) != null;
|
|
}
|
|
|
|
/// Get all supported targets for a link type
|
|
pub fn getSupportedTargets(self: TargetsConfig, link_type: LinkType) []const TargetLinkSpec {
|
|
return switch (link_type) {
|
|
.exe => self.exe,
|
|
.static_lib => self.static_lib,
|
|
.shared_lib => self.shared_lib,
|
|
};
|
|
}
|
|
|
|
/// Create a TargetsConfig from a parsed AST
|
|
/// Returns null if the platform header has no targets section
|
|
pub fn fromAST(allocator: Allocator, ast: anytype) !?TargetsConfig {
|
|
const NodeStore = parse.NodeStore;
|
|
|
|
const store: *const NodeStore = &ast.store;
|
|
|
|
// Get the file node first, then get the header from it
|
|
const file = store.getFile();
|
|
const header = store.getHeader(file.header);
|
|
|
|
// Only platform headers have targets
|
|
const platform = switch (header) {
|
|
.platform => |p| p,
|
|
else => return null,
|
|
};
|
|
|
|
// If no targets section, return null
|
|
const targets_section_idx = platform.targets orelse return null;
|
|
const targets_section = store.getTargetsSection(targets_section_idx);
|
|
|
|
// Extract files_dir from string literal token (StringPart token)
|
|
const files_dir: ?[]const u8 = if (targets_section.files_path) |tok_idx|
|
|
ast.resolve(tok_idx)
|
|
else
|
|
null;
|
|
|
|
// Convert exe link type
|
|
var exe_specs = std.array_list.Managed(TargetLinkSpec).init(allocator);
|
|
errdefer exe_specs.deinit();
|
|
|
|
if (targets_section.exe) |exe_idx| {
|
|
const link_type = store.getTargetLinkType(exe_idx);
|
|
const entry_indices = store.targetEntrySlice(link_type.entries);
|
|
|
|
for (entry_indices) |entry_idx| {
|
|
const entry = store.getTargetEntry(entry_idx);
|
|
|
|
// Parse target name from token
|
|
const target_name = ast.resolve(entry.target);
|
|
const target = RocTarget.fromString(target_name) orelse continue; // Skip unknown targets
|
|
|
|
// Convert files
|
|
var link_items = std.array_list.Managed(LinkItem).init(allocator);
|
|
errdefer link_items.deinit();
|
|
|
|
const file_indices = store.targetFileSlice(entry.files);
|
|
for (file_indices) |file_idx| {
|
|
const target_file = store.getTargetFile(file_idx);
|
|
|
|
switch (target_file) {
|
|
.string_literal => |tok| {
|
|
// The tok points to StringPart token containing the path
|
|
const path = ast.resolve(tok);
|
|
try link_items.append(.{ .file_path = path });
|
|
},
|
|
.special_ident => |tok| {
|
|
const ident = ast.resolve(tok);
|
|
if (std.mem.eql(u8, ident, "app")) {
|
|
try link_items.append(.app);
|
|
} else if (std.mem.eql(u8, ident, "win_gui")) {
|
|
try link_items.append(.win_gui);
|
|
}
|
|
// Skip unknown special identifiers
|
|
},
|
|
.malformed => continue, // Skip malformed entries
|
|
}
|
|
}
|
|
|
|
try exe_specs.append(.{
|
|
.target = target,
|
|
.items = try link_items.toOwnedSlice(),
|
|
});
|
|
}
|
|
}
|
|
|
|
// Convert static_lib link type
|
|
var static_lib_specs = std.array_list.Managed(TargetLinkSpec).init(allocator);
|
|
errdefer static_lib_specs.deinit();
|
|
|
|
if (targets_section.static_lib) |static_lib_idx| {
|
|
const link_type = store.getTargetLinkType(static_lib_idx);
|
|
const entry_indices = store.targetEntrySlice(link_type.entries);
|
|
|
|
for (entry_indices) |entry_idx| {
|
|
const entry = store.getTargetEntry(entry_idx);
|
|
|
|
// Parse target name from token
|
|
const target_name = ast.resolve(entry.target);
|
|
const target = RocTarget.fromString(target_name) orelse continue; // Skip unknown targets
|
|
|
|
// Convert files
|
|
var link_items = std.array_list.Managed(LinkItem).init(allocator);
|
|
errdefer link_items.deinit();
|
|
|
|
const file_indices = store.targetFileSlice(entry.files);
|
|
for (file_indices) |file_idx| {
|
|
const target_file = store.getTargetFile(file_idx);
|
|
|
|
switch (target_file) {
|
|
.string_literal => |tok| {
|
|
// The tok points to StringPart token containing the path
|
|
const path = ast.resolve(tok);
|
|
try link_items.append(.{ .file_path = path });
|
|
},
|
|
.special_ident => |tok| {
|
|
const ident = ast.resolve(tok);
|
|
if (std.mem.eql(u8, ident, "app")) {
|
|
try link_items.append(.app);
|
|
} else if (std.mem.eql(u8, ident, "win_gui")) {
|
|
try link_items.append(.win_gui);
|
|
}
|
|
// Skip unknown special identifiers
|
|
},
|
|
.malformed => continue, // Skip malformed entries
|
|
}
|
|
}
|
|
|
|
try static_lib_specs.append(.{
|
|
.target = target,
|
|
.items = try link_items.toOwnedSlice(),
|
|
});
|
|
}
|
|
}
|
|
|
|
// shared_lib to be added later
|
|
const empty_specs: []const TargetLinkSpec = &.{};
|
|
|
|
return TargetsConfig{
|
|
.files_dir = files_dir,
|
|
.exe = try exe_specs.toOwnedSlice(),
|
|
.static_lib = try static_lib_specs.toOwnedSlice(),
|
|
.shared_lib = empty_specs,
|
|
};
|
|
}
|
|
};
|