319 lines
9.1 KiB
Plaintext
319 lines
9.1 KiB
Plaintext
// cell build [--target <target>] - Build a static cell binary for a project
|
|
// Collects all C files from cell source, scripts, and project packages,
|
|
// compiles them, and links into a single static executable.
|
|
|
|
var build = use('build')
|
|
var shop = use('shop')
|
|
var fd = use('fd')
|
|
var os = use('os')
|
|
|
|
var targets = [
|
|
"arm64-macos",
|
|
"x86_64-macos",
|
|
"x86_64-linux",
|
|
"arm64-linux",
|
|
"windows",
|
|
"playdate"
|
|
]
|
|
|
|
// Parse arguments
|
|
var target = null
|
|
for (var i = 0; i < args.length; i++) {
|
|
if (args[i] == '--target' || args[i] == '-t') {
|
|
if (i + 1 < args.length) {
|
|
target = args[i + 1]
|
|
i++
|
|
} else {
|
|
log.error("--target requires an argument")
|
|
log.console("Available targets: " + targets.join(', '))
|
|
$_.stop()
|
|
}
|
|
} else if (args[i] == '--help' || args[i] == '-h') {
|
|
log.console("Usage: cell build [--target <target>]")
|
|
log.console("Build a static cell binary for the current project.")
|
|
log.console("")
|
|
log.console("Options:")
|
|
log.console(" --target, -t <target> Cross-compile for target platform")
|
|
log.console("")
|
|
log.console("Available targets: " + targets.join(', '))
|
|
$_.stop()
|
|
} else if (args[i] == '--list-targets') {
|
|
log.console("Available targets:")
|
|
for (var t = 0; t < targets.length; t++) {
|
|
log.console(" " + targets[t])
|
|
}
|
|
$_.stop()
|
|
}
|
|
}
|
|
|
|
// Resolve target
|
|
target = build.resolve_target(target)
|
|
if (target) {
|
|
log.console("Building for target: " + target)
|
|
} else {
|
|
log.console("Building for host platform")
|
|
}
|
|
|
|
// Find cell package - it should be at ~/work/cell or a dependency
|
|
var cell_dir = null
|
|
|
|
// First check if we're in the cell directory itself
|
|
if (fd.is_file('source/cell.c') && fd.is_file('source/quickjs.c')) {
|
|
cell_dir = '.'
|
|
log.console("Building from cell source directory")
|
|
} else {
|
|
// Check for cell as a local path dependency or linked package
|
|
var config = shop.load_config()
|
|
if (config && config.dependencies && config.dependencies.cell) {
|
|
var pkg = config.dependencies.cell
|
|
var parsed = shop.parse_package(pkg)
|
|
var pkg_dir = '.cell/modules/' + parsed.path
|
|
if (fd.is_file(pkg_dir + '/source/cell.c')) {
|
|
cell_dir = pkg_dir
|
|
log.console("Using cell from dependency: " + pkg_dir)
|
|
}
|
|
}
|
|
|
|
// Fallback: try ~/work/cell
|
|
if (!cell_dir) {
|
|
var home_cell = os.getenv('HOME') + '/work/cell'
|
|
if (fd.is_file(home_cell + '/source/cell.c')) {
|
|
cell_dir = home_cell
|
|
log.console("Using cell from: " + cell_dir)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!cell_dir) {
|
|
log.error("Could not find cell source. Add cell as a dependency or run from cell directory.")
|
|
$_.stop()
|
|
}
|
|
|
|
// Collect all C files from cell
|
|
var source_files = build.list_files(cell_dir + '/source')
|
|
var script_files = build.list_files(cell_dir + '/scripts')
|
|
|
|
// Prefix with directory
|
|
var all_files = []
|
|
for (var i = 0; i < source_files.length; i++) {
|
|
all_files.push('source/' + source_files[i])
|
|
}
|
|
for (var i = 0; i < script_files.length; i++) {
|
|
all_files.push('scripts/' + script_files[i])
|
|
}
|
|
|
|
// Select C files for target
|
|
var c_files = build.select_c_files(all_files, target)
|
|
|
|
// Debug: show which files were selected
|
|
if (target == 'playdate') {
|
|
for (var i = 0; i < c_files.length; i++) {
|
|
if (c_files[i].indexOf('fd') >= 0 || c_files[i].indexOf('main') >= 0) {
|
|
log.console(" Selected: " + c_files[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
log.console("Found " + text(c_files.length) + " C files to compile")
|
|
|
|
// Get build directory
|
|
var build_dir = build.get_build_dir(target)
|
|
build.ensure_dir(build_dir)
|
|
|
|
// Load cell config for platform-specific flags
|
|
var cell_config = build.load_config(cell_dir)
|
|
var target_system = build.get_target_system(target)
|
|
var platform = target_system || os.platform()
|
|
|
|
var cflags = build.get_flags(cell_config, platform, 'CFLAGS')
|
|
var ldflags = build.get_flags(cell_config, platform, 'LDFLAGS')
|
|
|
|
// Compile options
|
|
var compile_options = {
|
|
target: target,
|
|
cflags: cflags,
|
|
includes: [cell_dir + '/source'],
|
|
defines: {}
|
|
}
|
|
|
|
// Add target-specific defines
|
|
if (target == 'playdate') {
|
|
compile_options.defines.TARGET_PLAYDATE = true
|
|
}
|
|
|
|
// Compile all C files
|
|
var objects = []
|
|
for (var i = 0; i < c_files.length; i++) {
|
|
var src = cell_dir + '/' + c_files[i]
|
|
var obj = build_dir + '/' + c_files[i] + '.o'
|
|
|
|
// Check if recompilation needed
|
|
var needs_compile = true
|
|
if (fd.is_file(obj)) {
|
|
var src_stat = fd.stat(src)
|
|
var obj_stat = fd.stat(obj)
|
|
if (src_stat && obj_stat && src_stat.mtime <= obj_stat.mtime) {
|
|
needs_compile = false
|
|
}
|
|
}
|
|
|
|
if (needs_compile) {
|
|
var result = build.compile_file(src, obj, compile_options)
|
|
if (!result) {
|
|
log.error("Build failed")
|
|
$_.stop()
|
|
}
|
|
}
|
|
objects.push(obj)
|
|
}
|
|
|
|
// Collect C files from project packages
|
|
var packages = shop.list_packages()
|
|
for (var p = 0; p < packages.length; p++) {
|
|
var pkg = packages[p]
|
|
var parsed = shop.parse_package(pkg)
|
|
var pkg_dir = '.cell/modules/' + parsed.path
|
|
|
|
if (!fd.is_dir(pkg_dir)) continue
|
|
|
|
var pkg_files = build.list_files(pkg_dir)
|
|
var pkg_c_files = build.select_c_files(pkg_files, target)
|
|
|
|
var pkg_config = build.load_config(pkg_dir)
|
|
var pkg_ldflags = build.get_flags(pkg_config, platform, 'LDFLAGS')
|
|
if (pkg_ldflags) {
|
|
if (ldflags != '') ldflags += ' '
|
|
ldflags += pkg_ldflags
|
|
}
|
|
|
|
if (pkg_c_files.length > 0) {
|
|
log.console("Compiling " + text(pkg_c_files.length) + " C files from " + parsed.path)
|
|
|
|
var pkg_cflags = build.get_flags(pkg_config, platform, 'CFLAGS')
|
|
|
|
// Create symbol prefix for package
|
|
var use_prefix = 'js_' + parsed.path.replace(/\//g, '_').replace(/\./g, '_').replace(/-/g, '_') + '_'
|
|
|
|
for (var f = 0; f < pkg_c_files.length; f++) {
|
|
var src = pkg_dir + '/' + pkg_c_files[f]
|
|
var obj = build_dir + '/packages/' + parsed.path + '/' + pkg_c_files[f] + '.o'
|
|
|
|
var safe_name = pkg_c_files[f].substring(0, pkg_c_files[f].lastIndexOf('.')).replace(/\//g, '_').replace(/-/g, '_')
|
|
var use_name = use_prefix + safe_name + '_use'
|
|
|
|
var pkg_options = {
|
|
target: target,
|
|
cflags: pkg_cflags,
|
|
includes: [cell_dir + '/source', pkg_dir],
|
|
defines: { CELL_USE_NAME: use_name },
|
|
module_dir: pkg_dir
|
|
}
|
|
|
|
var needs_compile = true
|
|
if (fd.is_file(obj)) {
|
|
var src_stat = fd.stat(src)
|
|
var obj_stat = fd.stat(obj)
|
|
if (src_stat && obj_stat && src_stat.mtime <= obj_stat.mtime) {
|
|
needs_compile = false
|
|
}
|
|
}
|
|
|
|
if (needs_compile) {
|
|
var result = build.compile_file(src, obj, pkg_options)
|
|
if (!result) {
|
|
log.error("Build failed")
|
|
$_.stop()
|
|
}
|
|
}
|
|
objects.push(obj)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Collect C files from local project (only if not building from cell source directory)
|
|
if (cell_dir != '.') {
|
|
var local_config = shop.load_config() || {}
|
|
var local_ldflags = build.get_flags(local_config, platform, 'LDFLAGS')
|
|
if (local_ldflags) {
|
|
if (ldflags != '') ldflags += ' '
|
|
ldflags += local_ldflags
|
|
}
|
|
|
|
var local_files = build.list_files('.')
|
|
var local_c_files = build.select_c_files(local_files, target)
|
|
|
|
if (local_c_files.length > 0) {
|
|
log.console("Compiling " + text(local_c_files.length) + " local C files")
|
|
|
|
var local_cflags = build.get_flags(local_config, platform, 'CFLAGS')
|
|
|
|
for (var f = 0; f < local_c_files.length; f++) {
|
|
var src = local_c_files[f]
|
|
var obj = build_dir + '/local/' + local_c_files[f] + '.o'
|
|
|
|
var safe_name = local_c_files[f].substring(0, local_c_files[f].lastIndexOf('.')).replace(/\//g, '_').replace(/-/g, '_')
|
|
var use_name = 'js_local_' + safe_name + '_use'
|
|
|
|
var local_options = {
|
|
target: target,
|
|
cflags: local_cflags,
|
|
includes: [cell_dir + '/source', '.'],
|
|
defines: { CELL_USE_NAME: use_name }
|
|
}
|
|
|
|
var needs_compile = true
|
|
if (fd.is_file(obj)) {
|
|
var src_stat = fd.stat(src)
|
|
var obj_stat = fd.stat(obj)
|
|
if (src_stat && obj_stat && src_stat.mtime <= obj_stat.mtime) {
|
|
needs_compile = false
|
|
}
|
|
}
|
|
|
|
if (needs_compile) {
|
|
var result = build.compile_file(src, obj, local_options)
|
|
if (!result) {
|
|
log.error("Build failed")
|
|
$_.stop()
|
|
}
|
|
}
|
|
objects.push(obj)
|
|
}
|
|
}
|
|
}
|
|
|
|
log.console("Compiled " + text(objects.length) + " object files")
|
|
|
|
// Link into executable
|
|
var exe_ext = build.get_exe_ext(target)
|
|
var exe_name = build_dir + '/cell' + exe_ext
|
|
|
|
var link_options = {
|
|
target: target,
|
|
ldflags: ldflags,
|
|
libs: []
|
|
}
|
|
|
|
// Add platform-specific libraries
|
|
if (!target || platform == 'macOS' || platform == 'darwin') {
|
|
// macOS needs no extra libs for static build
|
|
} else if (target == 'windows') {
|
|
link_options.libs.push('ws2_32')
|
|
link_options.libs.push('winmm')
|
|
}
|
|
|
|
var result = build.link_executable(objects, exe_name, link_options)
|
|
if (!result) {
|
|
log.error("Linking failed")
|
|
$_.stop()
|
|
}
|
|
|
|
// TODO: Append core.qop to executable
|
|
// For now, just report success
|
|
log.console("")
|
|
log.console("Build complete: " + exe_name)
|
|
log.console("")
|
|
log.console("Note: To create a fully functional cell binary, you need to append core.qop:")
|
|
log.console(" cat core.qop >> " + exe_name)
|
|
|
|
$_.stop() |