dynamic works; static works for non cell builds

This commit is contained in:
2025-12-08 15:27:20 -06:00
parent dbc1b2c31e
commit aae267a6e9
5 changed files with 718 additions and 288 deletions

View File

@@ -2,7 +2,7 @@
CFLAGS = "-Isource -Wno-incompatible-pointer-types -Wno-missing-braces -Wno-strict-prototypes -Wno-unused-function -Wno-int-conversion"
LDFLAGS = "-lstdc++ -lm"
[compilation.macOS]
[compilation.macos_arm64]
CFLAGS = "-x objective-c"
LDFLAGS = "-framework CoreFoundation -framework CFNetwork"
@@ -10,4 +10,4 @@ LDFLAGS = "-framework CoreFoundation -framework CFNetwork"
CFLAGS = "-DMINIZ_NO_TIME -DTARGET_EXTENSION -DTARGET_PLAYDATE -I$PLAYDATE_SDK_PATH/C_API"
[compilation.windows]
LDFLAGS = "-lbcrypt -lwinhttp -static-libgcc -static-libstdc++"
LDFLAGS = "-lws2_32 -lwinmm -liphlpapi -lbcrypt -lwinhttp -static-libgcc -static-libstdc++"

View File

@@ -1,12 +1,13 @@
// cell build [options] [actor] - Build cell binary
//
// Modes:
// cell build -d Build libcell_runtime.dylib (development mode)
// cell build [actor] Build static binary for project (release mode)
// cell build -s [actor] Build fully static binary (no external script lookup)
// cell build -d Build dynamic libraries for all packages
// cell build -d --target <tar> Build dynamic libraries for a specific target
// cell build -o <name> Build static binary for current project
// cell build --target <tar> -o <n> Build static binary for target platform
//
// The actor argument specifies the entry point (e.g., "accio" for accio.ce)
// If no actor is specified, builds cell itself.
// If no actor is specified for static builds, builds cell itself.
var build = use('build')
var shop = use('shop')
@@ -17,12 +18,14 @@ var utf8 = use('utf8')
var json = use('json')
var targets = [
"arm64-macos",
"x86_64-macos",
"x86_64-linux",
"arm64-linux",
"macos_arm64",
"macos_x86_64",
"linux",
"linux_arm64",
"windows",
"playdate"
"windows_i686",
"playdate",
"emscripten"
]
// Parse arguments
@@ -30,6 +33,8 @@ var target = null
var dynamic_mode = false
var static_only = false // -s flag: don't look outside pack for scripts
var actor = null
var output_name = null
var target_package = null
for (var i = 0; i < args.length; i++) {
if (args[i] == '--target' || args[i] == '-t') {
@@ -41,22 +46,47 @@ for (var i = 0; i < args.length; i++) {
log.console("Available targets: " + targets.join(', '))
$_.stop()
}
} else if (args[i] == '-p' || args[i] == '--package') {
if (i + 1 < args.length) {
target_package = args[i + 1]
dynamic_mode = true
i++
} else {
log.error("-p requires a package name")
$_.stop()
}
} else if (args[i] == '-d' || args[i] == '--dynamic') {
dynamic_mode = true
} else if (args[i] == '-s' || args[i] == '--static-only') {
static_only = true
} else if (args[i] == '-o' || args[i] == '--output') {
if (i + 1 < args.length) {
output_name = args[i + 1]
i++
} else {
log.error("-o requires an output name")
$_.stop()
}
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell build [options] [actor]")
log.console("Build a cell binary for the current project.")
log.console("Build cell binaries or dynamic libraries.")
log.console("")
log.console("Options:")
log.console(" -d, --dynamic Build libcell_runtime.dylib (dev mode)")
log.console(" -d, --dynamic Build dynamic libraries for all packages")
log.console(" -p, --package <pkg> Build dynamic library for specific package")
log.console(" -o, --output <name> Output name for static binary")
log.console(" -s, --static-only Build fully static (no external scripts)")
log.console(" --target, -t <target> Cross-compile for target platform")
log.console("")
log.console("Arguments:")
log.console(" actor Entry point actor (e.g., 'accio' for accio.ce)")
log.console("")
log.console("Examples:")
log.console(" cell build -d Build dynamic libs for host")
log.console(" cell build -d --target windows Build dynamic libs for Windows")
log.console(" cell build -o myapp Build static binary 'myapp'")
log.console(" cell build --target windows -o myapp Cross-compile static binary")
log.console("")
log.console("Available targets: " + targets.join(', '))
$_.stop()
} else if (args[i] == '--list-targets') {
@@ -70,126 +100,538 @@ for (var i = 0; i < args.length; i++) {
}
}
// Resolve target
// Resolve target - if not specified for dynamic mode, detect host
if (!target) {
var host_platform = os.platform()
var host_arch = os.arch ? os.arch() : 'arm64' // Default to arm64 if not available
if (host_platform == 'macOS' || host_platform == 'darwin') {
target = host_arch == 'x86_64' ? 'macos_x86_64' : 'macos_arm64'
} else if (host_platform == 'Linux') {
target = host_arch == 'x86_64' ? 'linux' : 'linux_arm64'
} else if (host_platform == 'Windows') {
target = 'windows'
}
if (target) log.console("Detected host target: " + target)
}
if (target && !build.has_target(target)) {
log.console("Available targets: " + targets.join(', '))
throw new Error("Invalid target: " + target)
}
// Find cell package - it should be at ~/work/cell or a dependency
function find_cell_dir() {
// First check if we're in the cell directory itself
if (fd.is_file('source/cell.c') && fd.is_file('source/quickjs.c')) {
log.console("Building from cell source directory")
return '.'
var target_system = build.get_target_system(target)
var platform = target_system || os.platform()
var dylib_ext = build.get_dylib_ext(target)
// Get build directory for a target (with subdirectory for cross-compilation)
function get_target_build_dir() {
var base = shop.get_shop_path() + '/build'
if (target) return base + '/' + target
return base + '/host'
}
// Filter out main*.c files (only for static builds)
function filter_main_files(files) {
return files.filter(function(f) {
var basename = f
var slash = f.lastIndexOf('/')
if (slash >= 0) basename = f.substring(slash + 1)
// Exclude main.c and main_*.c files
if (basename == 'main.c') return false
if (basename.startsWith('main_') && basename.endsWith('.c')) return false
return true
})
}
// Get the appropriate main file for a target (for static builds)
function get_main_file(cell_dir, target_name) {
// Check for target-specific main first
if (target_name) {
var target_main = cell_dir + '/source/main_' + target_name + '.c'
if (fd.is_file(target_main)) return target_main
}
// Fall back to generic main.c
var generic_main = cell_dir + '/source/main.c'
if (fd.is_file(generic_main)) return generic_main
return null
}
// Resolve relative paths in LDFLAGS to absolute paths relative to a base directory
// This handles -L and -Wl,-rpath flags with relative paths
function resolve_ldflags(flags, base_dir) {
if (!flags || !base_dir) return flags
var parts = flags.split(' ')
var result = []
for (var i = 0; i < parts.length; i++) {
var part = parts[i]
if (!part) continue
// Handle -L<path>
if (part.startsWith('-L') && part.length > 2) {
var path = part.substring(2)
if (!path.startsWith('/') && !path.startsWith('$')) {
part = '-L' + base_dir + '/' + path
}
}
// Handle -L <path> (space separated)
else if (part == '-L' && i + 1 < parts.length) {
result.push(part)
i++
var path = parts[i]
if (!path.startsWith('/') && !path.startsWith('$')) {
path = base_dir + '/' + path
}
result.push(path)
continue
}
// Check for cell as a local path dependency or linked package
result.push(part)
}
return result.join(' ')
}
// Find cell package (core)
function find_cell_dir() {
// Check Shop Core Dir first (this is where cell is linked/installed)
var core_dir = shop.get_core_dir()
if (fd.is_file(core_dir + '/source/cell.c')) {
return core_dir
}
// Check if we're in the cell directory itself
if (fd.is_file('source/cell.c') && fd.is_file('source/quickjs.c')) {
return fd.realpath('.')
}
// Check for cell as a dependency
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 = shop.get_shop_path() + '/modules/' + parsed.path
var pkg_dir = shop.get_shop_path() + '/packages/' + parsed.path
if (fd.is_file(pkg_dir + '/source/cell.c')) {
log.console("Using cell from dependency: " + pkg_dir)
return pkg_dir
}
}
// Check Shop Core Dir
var core_dir = shop.get_core_dir()
if (fd.is_file(core_dir + '/source/cell.c')) {
log.console("Using cell from core: " + core_dir)
return core_dir
}
// Fallback: try ~/work/cell
var home_cell = os.getenv('HOME') + '/work/cell'
if (fd.is_file(home_cell + '/source/cell.c')) {
log.console("Using cell from: " + home_cell)
return home_cell
}
return null
}
var cell_dir = find_cell_dir()
if (!cell_dir) {
log.error("Could not find cell source. Add cell as a dependency or run from cell directory.")
$_.stop()
}
// Build core cellmod.dylib (must be done first for dynamic builds)
function build_core_cellmod(cell_dir, build_dir) {
log.console("Building core cellmod" + dylib_ext + "...")
// Collect all C files from cell
var source_files = build.list_files(cell_dir + '/source')
var script_files = build.list_files(cell_dir + '/scripts')
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++) {
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++) {
}
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)
// Select C files for target, then filter out main*.c
var c_files = build.select_c_files(all_files, target)
c_files = filter_main_files(c_files)
// For dynamic mode, exclude main.c
if (dynamic_mode) {
c_files = c_files.filter(function(f) {
return f.indexOf('main.c') < 0 && f.indexOf('main_') < 0
})
log.console("Dynamic mode: excluding main.c")
}
log.console("Found " + text(c_files.length) + " C files for core")
log.console("Found " + text(c_files.length) + " C files to compile")
var cell_config = build.load_config(cell_dir)
var cflags = '-fPIC ' + build.get_flags(cell_config, platform, 'CFLAGS', target)
var ldflags = build.get_flags(cell_config, platform, 'LDFLAGS', target)
// Get build directory
var build_dir = dynamic_mode ? shop.get_shop_path() + '/build/dynamic' : 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')
// For dynamic builds, add -fPIC
if (dynamic_mode) {
cflags = '-fPIC ' + cflags
}
// Compile options
var compile_options = {
var compile_options = {
target: target,
cflags: cflags,
includes: [cell_dir, cell_dir + '/source'], // Add cell root and source for includes
includes: [cell_dir, cell_dir + '/source'],
defines: {},
module_dir: cell_dir // Ensure we run compilation from cell dir context
}
module_dir: cell_dir
}
// Add target-specific defines
if (target == 'playdate') {
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 = c_files[i] // Relative to module_dir because we set it above?
// Adjusted:
var objects = []
for (var i = 0; i < c_files.length; i++) {
var src_rel = c_files[i]
var obj_base = shop.get_global_build_dir() + '/cell' // Build cell into its own area?
var obj = build_dir + (dynamic_mode ? '/' : '/static/') + src_rel + '.o' // Separate static build
var src_abs = cell_dir + '/' + src_rel
var obj = build_dir + '/core/' + src_rel + '.o'
var needs_compile = true
if (fd.is_file(obj)) {
var src_stat = fd.stat(src_abs)
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_abs, obj, compile_options)
if (!result) {
log.error("Failed to compile " + src_abs)
return null
}
}
objects.push(obj)
}
// Link into shared library
var lib_name = build_dir + '/core/libcellmod' + dylib_ext
build.ensure_dir(build_dir + '/core')
var link_flags = '-shared -fPIC'
if (ldflags) link_flags += ' ' + ldflags
var objs_str = objects.join(' ')
var cc = build.get_cpp(target)
var cmd = cc + ' ' + link_flags + ' ' + objs_str + ' -o ' + lib_name
log.console(cmd)
log.console("Linking " + lib_name)
var ret = os.system(cmd)
if (ret != 0) {
log.error("Failed to link core cellmod")
return null
}
log.console("Built " + lib_name)
return lib_name
}
// Build a package's cellmod.dylib
function build_package_cellmod(pkg_path, pkg_dir, build_dir, core_lib) {
var pkg_files = build.list_files(pkg_dir)
var c_files = build.select_c_files(pkg_files, target)
c_files = filter_main_files(c_files)
if (c_files.length == 0) {
return null // No C files to compile
}
log.console("Building " + pkg_path + " (" + text(c_files.length) + " C files)...")
var pkg_config = build.load_config(pkg_dir)
var cflags = '-fPIC ' + build.get_flags(pkg_config, platform, 'CFLAGS', target)
var ldflags = build.get_flags(pkg_config, platform, 'LDFLAGS', target)
var use_prefix = 'js_' + pkg_path.replace(/\//g, '_').replace(/\./g, '_').replace(/-/g, '_') + '_'
var objects = []
for (var i = 0; i < c_files.length; i++) {
var src_rel = c_files[i]
var src_abs = pkg_dir + '/' + src_rel
var obj = build_dir + '/packages/' + pkg_path + '/' + src_rel + '.o'
var safe_name = src_rel.substring(0, src_rel.lastIndexOf('.')).replace(/\//g, '_').replace(/-/g, '_')
var use_name = use_prefix + safe_name + '_use'
var compile_options = {
target: target,
cflags: cflags,
includes: [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_abs)
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_abs, obj, compile_options)
if (!result) {
log.error("Failed to compile " + src_abs)
return false
}
}
objects.push(obj)
}
// Link into shared library, linking against core cellmod
var lib_dir = build_dir + '/packages/' + pkg_path
build.ensure_dir(lib_dir)
var lib_name = lib_dir + '/libcellmod' + dylib_ext
var link_flags = '-shared -fPIC'
if (platform == 'macOS' || platform == 'darwin') {
// Link against core cellmod
link_flags += ' -L' + build_dir + '/core -Wl,-rpath,@loader_path/../../core'
} else if (platform == 'Linux' || platform == 'linux') {
link_flags += ' -L' + build_dir + '/core -Wl,-rpath,$ORIGIN/../../core'
} else if (platform == 'Windows' || platform == 'windows') {
link_flags += ' -L' + build_dir + '/core'
}
link_flags += ' -lcellmod'
if (ldflags) link_flags += ' ' + ldflags
var objs_str = objects.join(' ')
var cc = build.get_cpp(target)
var cmd = cc + ' ' + link_flags + ' ' + objs_str + ' -o ' + lib_name
log.console("Linking " + lib_name)
// Run from package directory so relative paths in LDFLAGS work
var ret = os.system('cd "' + pkg_dir + '" && ' + cmd)
if (ret != 0) {
log.error("Failed to link " + pkg_path)
return false
}
return lib_name
}
// ============================================================================
// MAIN BUILD LOGIC
// ============================================================================
var cell_dir = find_cell_dir()
if (!cell_dir) {
log.error("Could not find cell source. Link cell to core or add as dependency.")
$_.stop()
}
log.console("Using cell from: " + cell_dir)
if (dynamic_mode) {
// ========================================================================
// DYNAMIC MODE: Build cellmod.dylib for all packages
// ========================================================================
var build_dir = get_target_build_dir()
build.ensure_dir(build_dir)
// 1. Build core cellmod first (everything else links to it)
var core_lib = build_core_cellmod(cell_dir, build_dir)
if (!core_lib) {
log.error("Failed to build core cellmod")
$_.stop()
}
// 2. Build cellmod for each installed package
var packages = shop.list_shop_packages()
var built_count = 1 // core
for (var i = 0; i < packages.length; i++) {
var info = packages[i]
var pkg = info.package
if (!pkg || pkg == 'core') continue
if (target_package && pkg != target_package) continue
var parsed = shop.parse_package(pkg)
var pkg_dir = shop.get_shop_path() + '/packages/' + parsed.path
if (!fd.is_dir(pkg_dir)) {
log.console("Skipping " + pkg + " (not found)")
continue
}
var result = build_package_cellmod(parsed.path, pkg_dir, build_dir, core_lib)
if (result) built_count++
}
// 3. Also build local project if we're in one
var local_config = shop.load_config()
if (!target_package && local_config && cell_dir != fd.realpath('.')) {
var local_files = build.list_files('.')
var local_c_files = build.select_c_files(local_files, target)
local_c_files = filter_main_files(local_c_files)
if (local_c_files.length > 0) {
log.console("Building local project...")
var local_cflags = '-fPIC ' + build.get_flags(local_config, platform, 'CFLAGS', target)
var local_ldflags = build.get_flags(local_config, platform, 'LDFLAGS', target)
var objects = []
for (var i = 0; i < local_c_files.length; i++) {
var src = local_c_files[i]
var obj = build_dir + '/local/' + src + '.o'
var safe_name = src.substring(0, src.lastIndexOf('.')).replace(/\//g, '_').replace(/-/g, '_')
var use_name = 'js_local_' + safe_name + '_use'
var compile_options = {
target: target,
cflags: local_cflags,
includes: ['.'],
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, compile_options)
if (!result) {
log.error("Failed to compile " + src)
$_.stop()
}
}
objects.push(obj)
}
// Link local cellmod
var local_lib = build_dir + '/local/libcellmod' + dylib_ext
build.ensure_dir(build_dir + '/local')
var link_flags = '-shared -fPIC'
if (platform == 'macOS' || platform == 'darwin') {
link_flags += ' -L' + build_dir + '/core -Wl,-rpath,@loader_path/../core'
} else {
link_flags += ' -L' + build_dir + '/core -Wl,-rpath,$ORIGIN/../core'
}
link_flags += ' -lcellmod'
if (local_ldflags) link_flags += ' ' + local_ldflags
var objs_str = objects.join(' ')
var cc = build.get_cc(target)
var cmd = cc + ' ' + link_flags + ' ' + objs_str + ' -o ' + local_lib
log.console("Linking " + local_lib)
var ret = os.system(cmd)
if (ret != 0) {
log.error("Failed to link local cellmod")
$_.stop()
}
built_count++
}
}
// Copy libraries to standard build location for runtime loading
// The cell binary expects libraries at ~/.cell/build/packages/... not ~/.cell/build/<target>/packages/...
var standard_build_dir = shop.get_shop_path() + '/build'
// Copy core
var core_src = build_dir + '/core/libcellmod' + dylib_ext
var core_dst_dir = standard_build_dir + '/core'
build.ensure_dir(core_dst_dir)
var core_dst = core_dst_dir + '/libcellmod' + dylib_ext
if (fd.is_file(core_src)) {
os.system('cp "' + core_src + '" "' + core_dst + '"')
}
// Copy package libraries
for (var i = 0; i < packages.length; i++) {
var info = packages[i]
var pkg = info.package
if (!pkg || pkg == 'core') continue
var parsed = shop.parse_package(pkg)
var pkg_src = build_dir + '/packages/' + parsed.path + '/libcellmod' + dylib_ext
if (fd.is_file(pkg_src)) {
var pkg_dst_dir = standard_build_dir + '/packages/' + parsed.path
build.ensure_dir(pkg_dst_dir)
var pkg_dst = pkg_dst_dir + '/libcellmod' + dylib_ext
os.system('cp "' + pkg_src + '" "' + pkg_dst + '"')
}
}
// Copy local library if built
var local_src = build_dir + '/local/libcellmod' + dylib_ext
if (fd.is_file(local_src)) {
var local_dst_dir = standard_build_dir + '/local'
build.ensure_dir(local_dst_dir)
var local_dst = local_dst_dir + '/libcellmod' + dylib_ext
os.system('cp "' + local_src + '" "' + local_dst + '"')
}
if (target_package && built_count == 1) {
log.error("Package '" + target_package + "' not found in shop or failed to build.")
}
log.console("")
log.console("Dynamic build complete: " + text(built_count) + " libraries built")
log.console("Build directory: " + build_dir)
log.console("Libraries copied to: " + standard_build_dir)
} else {
// ========================================================================
// STATIC MODE: Build a static executable
// ========================================================================
// Must be run from a directory with cell.toml
var local_config = shop.load_config()
if (!local_config && cell_dir != fd.realpath('.')) {
log.error("Static builds must be run from a project directory with cell.toml")
log.error("Or run from the cell source directory itself")
$_.stop()
}
var build_dir = get_target_build_dir() + '/static'
build.ensure_dir(build_dir)
// Collect all C files from cell (including main.c for static builds)
var source_files = build.list_files(cell_dir + '/source')
var script_files = build.list_files(cell_dir + '/scripts')
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])
}
var c_files = build.select_c_files(all_files, target)
// For static builds, we keep main.c (or target-specific main)
// But we need to filter out wrong main files
c_files = c_files.filter(function(f) {
var basename = f
var slash = f.lastIndexOf('/')
if (slash >= 0) basename = f.substring(slash + 1)
// Keep main.c, filter out main_*.c unless it matches our target
if (basename.startsWith('main_') && basename.endsWith('.c')) {
var main_target = basename.substring(5, basename.length - 2)
return target && target.indexOf(main_target) >= 0
}
return true
})
var cell_config = build.load_config(cell_dir)
var cflags = build.get_flags(cell_config, platform, 'CFLAGS', target)
var ldflags = build.get_flags(cell_config, platform, 'LDFLAGS', target)
var compile_options = {
target: target,
cflags: cflags,
includes: [cell_dir, cell_dir + '/source'],
defines: {},
module_dir: cell_dir
}
if (target == 'playdate') {
compile_options.defines.TARGET_PLAYDATE = true
}
log.console("Compiling " + text(c_files.length) + " cell C files...")
var objects = []
for (var i = 0; i < c_files.length; i++) {
var src_rel = c_files[i]
var src_abs = cell_dir + '/' + src_rel
var obj = build_dir + '/cell/' + src_rel + '.o'
// Check if recompilation needed
var needs_compile = true
if (fd.is_file(obj)) {
var src_stat = fd.stat(src_abs)
@@ -200,8 +642,6 @@ for (var i = 0; i < c_files.length; i++) {
}
if (needs_compile) {
// Ensure specific file includes if needed?
// The global includes should cover it.
var result = build.compile_file(src_abs, obj, compile_options)
if (!result) {
log.error("Build failed")
@@ -209,42 +649,40 @@ for (var i = 0; i < c_files.length; i++) {
}
}
objects.push(obj)
}
}
// For static builds, also compile package C files
if (!dynamic_mode) {
// Compile package C files
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 = shop.get_shop_path() + '/modules/' + parsed.path
var pkg_dir = shop.get_shop_path() + '/packages/' + 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)
pkg_c_files = filter_main_files(pkg_c_files)
if (pkg_c_files.length == 0) continue
log.console("Compiling " + text(pkg_c_files.length) + " C files from " + parsed.path)
var pkg_config = build.load_config(pkg_dir)
var pkg_ldflags = build.get_flags(pkg_config, platform, 'LDFLAGS')
var pkg_cflags = build.get_flags(pkg_config, platform, 'CFLAGS', target)
var pkg_ldflags = build.get_flags(pkg_config, platform, 'LDFLAGS', target)
if (pkg_ldflags) {
// Resolve relative paths in LDFLAGS to absolute paths
pkg_ldflags = resolve_ldflags(pkg_ldflags, pkg_dir)
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]
// Use shop to determine build directory for this package, instead of manual concat
var pkg_build_base = shop.get_build_dir(parsed.path)
var obj = pkg_build_base + '/' + pkg_c_files[f] + '.o'
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'
@@ -276,30 +714,30 @@ if (!dynamic_mode) {
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
}
// Compile local C files (if not building from cell directory)
if (cell_dir != fd.realpath('.')) {
var local_files = build.list_files('.')
var local_c_files = build.select_c_files(local_files, target)
local_c_files = filter_main_files(local_c_files)
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')
var local_cflags = build.get_flags(local_config, platform, 'CFLAGS', target)
var local_ldflags = build.get_flags(local_config, platform, 'LDFLAGS', target)
if (local_ldflags) {
// Resolve relative paths - local project is in current directory
local_ldflags = resolve_ldflags(local_ldflags, fd.realpath('.'))
if (ldflags != '') ldflags += ' '
ldflags += local_ldflags
}
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 obj = build_dir + '/local/' + src + '.o'
var safe_name = local_c_files[f].substring(0, local_c_files[f].lastIndexOf('.')).replace(/\//g, '_').replace(/-/g, '_')
var safe_name = src.substring(0, src.lastIndexOf('.')).replace(/\//g, '_').replace(/-/g, '_')
var use_name = 'js_local_' + safe_name + '_use'
var local_options = {
@@ -329,40 +767,13 @@ if (!dynamic_mode) {
}
}
}
}
log.console("Compiled " + text(objects.length) + " object files")
log.console("Compiled " + text(objects.length) + " object files")
// Link
if (dynamic_mode) {
// Link into shared library
var dylib_ext = build.get_dylib_ext(target)
var lib_name = build_dir + '/libcell_runtime' + dylib_ext
var link_flags = '-shared -fPIC'
if (platform == 'macOS' || platform == 'darwin') {
link_flags += ' -framework CoreFoundation -framework CFNetwork'
}
if (ldflags) link_flags += ' ' + ldflags
var objs_str = objects.join(' ')
var cmd = 'cc ' + link_flags + ' ' + objs_str + ' -o ' + lib_name
log.console("Linking " + lib_name)
var ret = os.system(cmd)
if (ret != 0) {
log.error("Linking failed")
$_.stop()
}
log.console("")
log.console("Build complete: " + lib_name)
log.console("")
log.console("To use: copy to /opt/homebrew/lib/ or set DYLD_LIBRARY_PATH")
} else {
// Link into executable
var exe_ext = build.get_exe_ext(target)
var exe_name = build_dir + '/cell' + exe_ext
// Use 'cell_bin' to avoid conflict with 'cell' directory containing object files
var exe_name = build_dir + '/cell_bin' + exe_ext
var link_options = {
target: target,
@@ -370,7 +781,6 @@ if (dynamic_mode) {
libs: []
}
// Add platform-specific libraries
if (!target || platform == 'macOS' || platform == 'darwin') {
link_options.ldflags += ' -framework CoreFoundation -framework CFNetwork'
} else if (target == 'windows') {
@@ -384,13 +794,11 @@ if (dynamic_mode) {
$_.stop()
}
// Create the pack (core.qop + project scripts)
// Create the pack (scripts)
var pack_path = build_dir + '/pack.qop'
// Collect scripts to pack
var pack_files = []
// Always include core scripts from cell
// Include core scripts
var core_scripts = fd.readdir(cell_dir + '/scripts')
for (var i = 0; i < core_scripts.length; i++) {
var f = core_scripts[i]
@@ -399,78 +807,29 @@ if (dynamic_mode) {
}
}
// If building a project (not cell itself), include project scripts
if (cell_dir != '.') {
// Include local scripts
var local_scripts = shop.list_modules()
for (var i = 0; i < local_scripts.length; i++) {
pack_files.push({ path: local_scripts[i], source: local_scripts[i] })
}
// Include package scripts
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 = shop.get_shop_path() + '/modules/' + parsed.path
var pkg_dir = shop.get_shop_path() + '/packages/' + parsed.path
var pkg_scripts = shop.list_modules(parsed.path)
for (var i = 0; i < pkg_scripts.length; i++) {
var pack_name = parsed.path + '/' + pkg_scripts[i]
pack_files.push({ path: pack_name, source: pkg_dir + '/' + pkg_scripts[i] })
}
}
}
// Create the pack
log.console("Creating pack with " + text(pack_files.length) + " scripts")
var writer = qop.write(pack_path)
// Add entry point configuration if actor is specified
if (actor) {
var entry_config = {
entry: actor,
static_only: static_only
}
writer.add_file('__entry__.json', utf8.encode(json.encode(entry_config)))
}
for (var i = 0; i < pack_files.length; i++) {
var pf = pack_files[i]
var data = fd.slurp(pf.source)
if (data) {
writer.add_file(pf.path, data)
// Include local scripts
if (cell_dir != fd.realpath('.')) {
var local_scripts = shop.list_modules()
for (var i = 0; i < local_scripts.length; i++) {
pack_files.push({ path: local_scripts[i], source: local_scripts[i] })
}
}
writer.finalize()
// Append pack to executable
var exe_data = fd.slurp(exe_name)
var pack_data = fd.slurp(pack_path)
fd.slurpwrite(exe_name, exe_data)
// Append pack data
var fh = fd.open(exe_name, 'a')
fd.write(fh, pack_data)
fd.close(fh)
var final_name = output_name || 'app' + exe_ext
// Determine final output name
var final_name
if (actor) {
// Named after the actor
var actor_base = actor
if (actor_base.endsWith('.ce')) actor_base = actor_base.substring(0, actor_base.length - 3)
if (actor_base.indexOf('/') >= 0) actor_base = actor_base.substring(actor_base.lastIndexOf('/') + 1)
final_name = actor_base + exe_ext
} else if (cell_dir != '.') {
// Named after the project directory
var cwd = fd.cwd()
var project_name = cwd.substring(cwd.lastIndexOf('/') + 1)
final_name = project_name + exe_ext
} else {
final_name = 'cell' + exe_ext
}
// Copy to project root
os.system('cp ' + exe_name + ' ' + final_name)
log.console("")

View File

@@ -337,6 +337,12 @@ Build.get_cc = function(target) {
return 'cc'
}
Build.get_cpp = function(target) {
var tc = Build.get_toolchain(target)
if (tc && tc.binaries && tc.binaries.cpp) return tc.binaries.cpp
return 'cpp'
}
// Get archiver command for target
Build.get_ar = function(target) {
var tc = Build.get_toolchain(target)
@@ -590,18 +596,20 @@ Build.compile_file = function(src_path, obj_path, options) {
var src_file = src_path
// If src_path is absolute, checking if it is inside module_dir
if (src_path.startsWith(module_dir + '/')) {
src_file = src_path.substring(module_dir.length + 1)
src_file = '"' + src_path.substring(module_dir.length + 1) + '"'
} else if (!src_path.startsWith('/')) {
src_file = '"$HERE/' + src_path + '"'
} else {
// It's absolute and outside module dir, use as is (or relative to root?)
// If we use absolute path, it should be fine
// It's absolute and outside module dir, use as is (quoted)
src_file = '"' + src_path + '"'
}
// Adjust output path to be absolute/relative to HERE
var out_file = obj_path
if (!out_file.startsWith('/')) {
out_file = '"$HERE/' + out_file + '"'
} else {
out_file = '"' + out_file + '"'
}
// If we're changing CWD to module_dir, we need to make sure out_file (if absolute) is still valid?
@@ -611,7 +619,7 @@ Build.compile_file = function(src_path, obj_path, options) {
// Likely in build.ce or shop.cm (build_package).
var cc_cmd = cc + ' -c' + compile_flags + ' ' + src_file + ' -o ' + out_file
full_cmd = 'HERE=$(pwd); cd ' + module_dir + ' && ' + cc_cmd
full_cmd = 'HERE=$(pwd); cd "' + module_dir + '" && ' + cc_cmd
} else {
// Standard compilation from current dir
var include_str = ''
@@ -707,16 +715,23 @@ Build.link_executable = function(objects, output, options) {
return output
}
// Get flags from config for a platform
Build.get_flags = function(config, platform, key) {
// Get flags from config for a platform/target
// Checks: compilation[key], compilation[platform][key], compilation[target][key]
Build.get_flags = function(config, platform, key, target) {
var flags = ''
if (config.compilation && config.compilation[key]) {
flags += config.compilation[key]
}
// Check platform (e.g., 'macOS', 'darwin', 'Linux')
if (config.compilation && config.compilation[platform] && config.compilation[platform][key]) {
if (flags != '') flags += ' '
flags += config.compilation[platform][key]
}
// Check target (e.g., 'macos_arm64', 'linux', 'windows')
if (target && target != platform && config.compilation && config.compilation[target] && config.compilation[target][key]) {
if (flags != '') flags += ' '
flags += config.compilation[target][key]
}
return flags
}

View File

@@ -826,6 +826,7 @@ function get_build_dir(pkg) {
}
Shop.get_build_dir = get_build_dir
Shop.get_global_build_dir = get_global_build_dir
function get_rel_path(path, pkg) {
if (!pkg) {
@@ -1019,7 +1020,7 @@ function resolve_c_symbol(path, package_context)
if (!static_only) {
// Then try dynamic library
var build_dir = get_build_dir(package_context)
var local_dl_name = build_dir + '/cellmod' + dylib_ext
var local_dl_name = build_dir + '/libcellmod' + dylib_ext
if (fd.is_file(local_dl_name)) {
if (!open_dls[local_dl_name])
@@ -1064,7 +1065,7 @@ function resolve_c_symbol(path, package_context)
// Then try dynamic library for package (skip in static_only mode)
if (!static_only) {
var pkg_build_dir = get_build_dir(canon_pkg)
var dl_path = pkg_build_dir + '/cellmod' + dylib_ext
var dl_path = pkg_build_dir + '/libcellmod' + dylib_ext
if (fd.is_file(dl_path)) {
if (!open_dls[dl_path]) open_dls[dl_path] = os.dylib_open(dl_path)
if (open_dls[dl_path]) {
@@ -1658,6 +1659,21 @@ Shop.module_reload = function(path, package) {
}
}
// Filter out main*.c files (they're only for static builds)
function is_main_file(file) {
var basename = file
var slash = file.lastIndexOf('/')
if (slash >= 0) basename = file.substring(slash + 1)
if (basename == 'main.c') return true
if (basename.startsWith('main_') && basename.endsWith('.c')) return true
return false
}
// Get the core build directory for linking
function get_core_build_dir() {
return get_global_build_dir() + '/core'
}
Shop.build_package = function(package)
{
if (package == 'local') package = null
@@ -1686,6 +1702,10 @@ Shop.build_package = function(package)
for (var i=0; i<files.length; i++) {
var file = files[i]
// Skip main*.c files - they're only for static builds
if (is_main_file(file)) continue
var src_path
if (!package) {
src_path = (current_package_path || '.') + '/' + file
@@ -1811,21 +1831,32 @@ Shop.build_package = function(package)
// Link if there are C objects
if (c_objects.length > 0) {
var lib_name = build_dir + '/cellmod' + dylib_ext
var lib_name = build_dir + '/libcellmod' + dylib_ext
var lib_meta_path = lib_name + '.meta'
var link_flags = '-fPIC -shared'
// Link against core cellmod.dylib
var core_build = get_core_build_dir()
if (platform == 'macOS') {
link_flags += ' -L' + core_build + ' -Wl,-rpath,@loader_path/../../core'
} else if (platform == 'Linux' || platform == 'linux') {
link_flags += ' -L' + core_build + ' -Wl,-rpath,$ORIGIN/../../core'
} else if (platform == 'Windows') {
link_flags += ' -L' + core_build
}
link_flags += ' -lcellmod'
var ldflags = get_flags(config, platform, 'LDFLAGS')
if (ldflags != '') link_flags += ' ' + ldflags
var temp_lib = 'cellmod' + dylib_ext
var temp_lib = 'libcellmod' + dylib_ext
var objs_str = ''
for (var i=0; i<c_objects.length; i++) {
objs_str += '"' + c_objects[i] + '" '
}
var link_cmd = 'cd ' + module_dir + ' && cc ' + link_flags + ' ' + objs_str + ' -lcell_runtime -lc -lc++ -o ' + temp_lib
var link_cmd = 'cd ' + module_dir + ' && cc ' + link_flags + ' ' + objs_str + ' -lc -lc++ -o ' + temp_lib
var link_cmd_hash = get_hash(link_cmd)
// Check if we need to relink

View File

@@ -1,20 +1,45 @@
// cell update [alias] - Update packages to latest versions
// cell update [alias] - Update packages to latest versions and rebuild dynamic libraries
//
// This command:
// 1. Updates all packages from their remote sources
// 2. Rebuilds all dynamic libraries via 'cell build -d'
var shop = use('shop')
var os = use('os')
var alias = args.length > 0 ? args[0] : null
var target = null
// Parse arguments
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 if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell update [options]")
log.console("Update packages and rebuild dynamic libraries.")
log.console("")
log.console("Options:")
log.console(" --target, -t <target> Build for specific target platform")
log.console("")
log.console("This command updates all installed packages from their remote")
log.console("sources, then rebuilds all dynamic libraries.")
$_.stop()
}
}
var packages = shop.list_shop_packages()
log.console("Checking for updates (" + packages.length + " packages)...")
// 1. Update all packages
for (var info of packages) {
var pack = info.package
if (!pack || pack == 'core') continue
log.console("Updating " + pack)
shop.update(pack)
shop.build_package(pack)
}
$_.stop()