This commit is contained in:
2025-12-09 23:19:25 -06:00
parent 657e342159
commit 37f2cff6ec
20 changed files with 1716 additions and 3139 deletions

View File

@@ -7,9 +7,17 @@
# The cell shop is at ~/.cell and core scripts are installed to ~/.cell/core
CELL_SHOP = $(HOME)/.cell
CELL_CORE = $(CELL_SHOP)/core
CELL_CORE_PACKAGE = $(CELL_SHOP)/packages/core
# Install core: symlink this directory to ~/.cell/core
install: bootstrap $(CELL_SHOP)
@echo "Linking cell core to $(CELL_CORE_PACKAGE)"
rm -rf $(CELL_CORE_PACKAGE)
ln -s $(PWD) $(CELL_CORE_PACKAGE)
cp cell /opt/homebrew/bin/
cp libcell_runtime.dylib /opt/homebrew/lib/
@echo "Core installed."
cell: libcell_runtime.dylib cell_main
cp cell_main cell
chmod +x cell
@@ -26,17 +34,6 @@ libcell_runtime.dylib: $(CELL_SHOP)/build/dynamic
cell_main: source/main.c libcell_runtime.dylib
cc -o cell_main source/main.c -L. -lcell_runtime -Wl,-rpath,@loader_path -Wl,-rpath,/opt/homebrew/lib
# Install core: symlink this directory to ~/.cell/core
install: bootstrap $(CELL_SHOP)
@echo "Linking cell core to $(CELL_CORE)"
rm -rf $(CELL_CORE)
rm -rf $(CELL_CORE_PACKAGE)
ln -s $(PWD) $(CELL_CORE)
ln -s $(PWD) $(CELL_CORE_PACKAGE)
cp cell /opt/homebrew/bin/
cp libcell_runtime.dylib /opt/homebrew/lib/
@echo "Core installed."
# Create the cell shop directories
$(CELL_SHOP):
mkdir -p $(CELL_SHOP)

View File

@@ -217,6 +217,7 @@ static const JSCFunctionListEntry js_writer_funcs[] = {
JSValue js_reader_mod(JSContext *js, JSValue self, int argc, JSValue *argv)
{
#ifndef MINIZ_NO_TIME
const char *file = JS_ToCString(js,argv[0]);
if (!file)
return JS_EXCEPTION;
@@ -243,6 +244,9 @@ JSValue js_reader_mod(JSContext *js, JSValue self, int argc, JSValue *argv)
}
return JS_NewFloat64(js, pstat.m_time);
#else
return JS_ThrowInternalError(js, "MINIZ_NO_TIME is defined");
#endif
}
JSValue js_reader_exists(JSContext *js, JSValue self, int argc, JSValue *argv)

View File

@@ -6,7 +6,7 @@ var time = use('time')
////////////////////////////////////////////////////////////////////////////////
// Test configurations
const iterations = {
def iterations = {
simple: 10000000,
medium: 1000000,
complex: 100000
@@ -154,7 +154,7 @@ function benchObjectCreation() {
this.y = y;
}
var constructorTime = measureTime(function() {
var defructorTime = measureTime(function() {
for (var i = 0; i < iterations.medium; i++) {
var p = new Point(i, i * 2);
}
@@ -179,7 +179,7 @@ function benchObjectCreation() {
return {
literalTime: literalTime,
constructorTime: constructorTime,
defructorTime: defructorTime,
prototypeTime: prototypeTime
};
}
@@ -342,9 +342,9 @@ var objResults = benchObjectCreation();
log.console(" Literal: " + objResults.literalTime.toFixed(3) + "s => " +
(iterations.medium / objResults.literalTime).toFixed(1) + " creates/sec [" +
(objResults.literalTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console(" Constructor: " + objResults.constructorTime.toFixed(3) + "s => " +
(iterations.medium / objResults.constructorTime).toFixed(1) + " creates/sec [" +
(objResults.constructorTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console(" Constructor: " + objResults.defructorTime.toFixed(3) + "s => " +
(iterations.medium / objResults.defructorTime).toFixed(1) + " creates/sec [" +
(objResults.defructorTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console(" Prototype: " + objResults.prototypeTime.toFixed(3) + "s => " +
(iterations.medium / objResults.prototypeTime).toFixed(1) + " creates/sec [" +
(objResults.prototypeTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");

877
build.ce
View File

@@ -1,845 +1,112 @@
// cell build [options] [actor] - Build cell binary
//
// Modes:
// 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
// cell build [options] - Build dynamic libraries locally for the current machine
//
// The actor argument specifies the entry point (e.g., "accio" for accio.ce)
// If no actor is specified for static builds, builds cell itself.
// Usage:
// cell build Build dynamic libraries for all packages
// cell build -p <pkg> Build dynamic library for specific package
// cell build -t <target> Cross-compile dynamic libraries for target platform
var build = use('build')
var shop = use('shop')
var pkg_tools = use('package')
var fd = use('fd')
var os = use('os')
var qop = use('qop')
var utf8 = use('utf8')
var json = use('json')
var targets = [
"macos_arm64",
"macos_x86_64",
"linux",
"linux_arm64",
"windows",
"windows_i686",
"playdate",
"emscripten"
]
// Parse arguments
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
var buildtype = 'release'
for (var i = 0; i < args.length; i++) {
if (args[i] == '--target' || args[i] == '-t') {
if (args[i] == '-t' || args[i] == '--target') {
if (i + 1 < args.length) {
target = args[i + 1]
i++
target = args[++i]
} else {
log.error("--target requires an argument")
log.console("Available targets: " + targets.join(', '))
log.error('-t requires a target')
$_.stop()
}
} else if (args[i] == '-p' || args[i] == '--package') {
if (i + 1 < args.length) {
target_package = args[i + 1]
dynamic_mode = true
i++
target_package = args[++i]
} else {
log.error("-p requires a package name")
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') {
} else if (args[i] == '-b' || args[i] == '--buildtype') {
if (i + 1 < args.length) {
output_name = args[i + 1]
i++
buildtype = args[++i]
if (buildtype != 'release' && buildtype != 'debug' && buildtype != 'minsize') {
log.error('Invalid buildtype: ' + buildtype + '. Must be release, debug, or minsize')
$_.stop()
}
} else {
log.error("-o requires an output name")
log.error('-b requires a buildtype (release, debug, minsize)')
$_.stop()
}
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell build [options] [actor]")
log.console("Build cell binaries or dynamic libraries.")
log.console("")
log.console("Options:")
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(', '))
log.console('Usage: cell build [options]')
log.console('')
log.console('Options:')
log.console(' -p, --package <pkg> Build specific package only')
log.console(' -t, --target <target> Cross-compile for target platform')
log.console(' -b, --buildtype <type> Build type: release, debug, minsize (default: release)')
log.console('')
log.console('Available targets: ' + build.list_targets().join(', '))
$_.stop()
} else if (args[i] == '--list-targets') {
log.console("Available targets:")
log.console('Available targets:')
var targets = build.list_targets()
for (var t = 0; t < targets.length; t++) {
log.console(" " + targets[t])
log.console(' ' + targets[t])
}
$_.stop()
} else if (!args[i].startsWith('-')) {
actor = args[i]
}
}
// Resolve target - if not specified for dynamic mode, detect host
// Detect target if not specified
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)
target = build.detect_host_target()
if (target) log.console('Target: ' + target)
}
if (target && !build.has_target(target)) {
log.console("Available targets: " + targets.join(', '))
throw new Error("Invalid target: " + target)
}
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
}
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() + '/packages/' + parsed.path
if (fd.is_file(pkg_dir + '/source/cell.c')) {
return pkg_dir
}
}
// Fallback: try ~/work/cell
var home_cell = os.getenv('HOME') + '/work/cell'
if (fd.is_file(home_cell + '/source/cell.c')) {
return home_cell
}
return null
}
// 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 + "...")
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])
}
// 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)
log.console("Found " + text(c_files.length) + " C files for core")
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)
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
}
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 + '/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.")
log.error('Invalid target: ' + target)
log.console('Available targets: ' + build.list_targets().join(', '))
$_.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'
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("Build failed")
$_.stop()
}
}
objects.push(obj)
}
// 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() + '/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_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
}
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: [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)
}
}
// 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', 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/' + src + '.o'
var safe_name = src.substring(0, src.lastIndexOf('.')).replace(/\//g, '_').replace(/-/g, '_')
var use_name = 'js_local_' + safe_name + '_use'
var local_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, 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)
// 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,
ldflags: ldflags,
libs: []
}
if (!target || platform == 'macOS' || platform == 'darwin') {
link_options.ldflags += ' -framework CoreFoundation -framework CFNetwork'
} 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()
}
// Create the pack (scripts)
var pack_path = build_dir + '/pack.qop'
var pack_files = []
// 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]
if (f.endsWith('.cm') || f.endsWith('.ce')) {
pack_files.push({ path: f, source: cell_dir + '/scripts/' + f })
}
}
// Include package scripts
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() + '/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] })
}
}
// 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] })
}
}
var final_name = output_name || 'app' + exe_ext
os.system('cp ' + exe_name + ' ' + final_name)
log.console("")
log.console("Build complete: " + final_name)
if (actor) {
log.console("Entry point: " + actor + ".ce")
} else {
log.console("Note: Run with an actor argument, e.g.: ./" + final_name + " main.ce")
}
var packages = shop.list_packages()
log.console('Preparing packages...')
for (var package of packages) {
if (package == 'core') continue
shop.extract(package)
}
$_.stop()
if (target_package) {
// Build single package
log.console('Building ' + target_package + '...')
try {
var lib = build.build_dynamic(target_package, target, buildtype)
if (lib) {
log.console('Built: ' + lib)
}
} catch (e) {
log.error('Build failed: ' + e)
$_.stop()
}
} else {
// Build all packages
log.console('Building all packages...')
var results = build.build_all_dynamic(target, buildtype)
var success = 0
var failed = 0
for (var i = 0; i < results.length; i++) {
if (results[i].library) {
success++
} else if (results[i].error) {
failed++
}
}
log.console('')
log.console('Build complete: ' + success + ' libraries built' + (failed > 0 ? ', ' + failed + ' failed' : ''))
}
$_.stop()

1016
build.cm

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ CFLAGS = "-x objective-c"
LDFLAGS = "-framework CoreFoundation -framework CFNetwork"
[compilation.playdate]
CFLAGS = "-DMINIZ_NO_TIME -DTARGET_EXTENSION -DTARGET_PLAYDATE -I$PLAYDATE_SDK_PATH/C_API"
CFLAGS = "-DMINIZ_NO_TIME -DTARGET_EXTENSION -DTARGET_PLAYDATE -I$LOCAL/PlaydateSDK/C_API"
[compilation.windows]
LDFLAGS = "-lws2_32 -lwinmm -liphlpapi -lbcrypt -lwinhttp -static-libgcc -static-libstdc++"

View File

@@ -1,7 +1,7 @@
// cell config - Manage system and actor configurations
var toml = use('toml')
var shop = use('shop')
var pkg = use('package')
var text = use('text')
function print_help() {
@@ -104,7 +104,7 @@ if (args.length == 0) {
return
}
var config = shop.load_config()
var config = pkg.load_config()
if (!config) {
log.error("Failed to load cell.toml")
$_.stop()
@@ -171,7 +171,7 @@ switch (command) {
}
set_nested(config, path, value)
shop.save_config(config)
pkg.save_config(config)
log.console("Set " + key + " = " + format_value(value))
break
@@ -230,7 +230,7 @@ switch (command) {
var value = parse_value(value_str)
set_nested(config.actors[actor_name], path, value)
shop.save_config(config)
pkg.save_config(config)
log.console("Set actors." + actor_name + "." + key + " = " + format_value(value))
break

4
fd.c
View File

@@ -568,13 +568,13 @@ JSC_CCALL(fd_slurpwrite,
int fd = open(str, O_WRONLY | O_CREAT | O_TRUNC, 0644);
JS_FreeCString(js, str);
if (fd < 0)
return JS_ThrowInternalError(js, "open failed: %s", strerror(errno));
return JS_ThrowInternalError(js, "open failed for %s: %s", str, strerror(errno));
ssize_t written = write(fd, data, len);
close(fd);
if (written != (ssize_t)len)
return JS_ThrowInternalError(js, "write failed: %s", strerror(errno));
return JS_ThrowInternalError(js, "write failed for %s: %s", str, strerror(errno));
return JS_NULL;
)

86
fetch.ce Normal file
View File

@@ -0,0 +1,86 @@
// cell fetch - Fetch package zips from remote sources
//
// This command ensures that the zip files on disk match what's in the lock file.
// For local packages, this is a no-op.
// For remote packages, downloads the zip if not present or hash mismatch.
//
// Usage:
// cell fetch - Fetch all packages
// cell fetch <package> - Fetch a specific package
var shop = use('shop')
// Parse arguments
var target_pkg = null
for (var i = 0; i < args.length; i++) {
if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell fetch [package]")
log.console("Fetch package zips from remote sources.")
log.console("")
log.console("Arguments:")
log.console(" package Optional package name to fetch. If omitted, fetches all.")
log.console("")
log.console("This command ensures that the zip files on disk match what's in")
log.console("the lock file. For local packages, this is a no-op.")
$_.stop()
} else if (!args[i].startsWith('-')) {
target_pkg = args[i]
}
}
var all_packages = shop.list_packages()
var lock = shop.load_lock()
var packages_to_fetch = []
if (target_pkg) {
// Fetch specific package
if (!all_packages.includes(target_pkg)) {
log.error("Package not found: " + target_pkg)
$_.stop()
}
packages_to_fetch.push(target_pkg)
} else {
// Fetch all packages
packages_to_fetch = all_packages
}
log.console("Fetching " + text(packages_to_fetch.length) + " package(s)...")
var success_count = 0
var skip_count = 0
var fail_count = 0
for (var pkg of packages_to_fetch) {
var entry = lock[pkg]
// Skip local packages
if (entry && entry.type == 'local') {
skip_count++
continue
}
// Skip core (handled separately)
if (pkg == 'core') {
skip_count++
continue
}
var result = shop.fetch(pkg)
if (result.ok) {
if (result.zip_blob) {
log.console("Fetched: " + pkg)
success_count++
} else {
skip_count++
}
} else {
log.error("Failed to fetch: " + pkg)
fail_count++
}
}
log.console("")
log.console("Fetch complete: " + text(success_count) + " fetched, " + text(skip_count) + " skipped, " + text(fail_count) + " failed")
$_.stop()

View File

@@ -50,6 +50,8 @@ the cell shop looks like this:
...
link.toml - temporary links for this cell shop
lock.toml - manifest of installed packages
lib
<dynamic libraries>
cache
<downloaded packages>
build - content addressed hash

View File

@@ -43,36 +43,44 @@ var utf8 = use_embed('utf8')
var js = use_embed('js')
var fd = use_embed('fd')
// Get the core path from C runtime
var core_path = hidden.core_path
if (!core_path) {
throw new Error('Core path not set - cell shop not properly initialized')
// Get the shop path from HOME environment
var home = os.getenv('HOME') || os.getenv('USERPROFILE')
if (!home) {
throw new Error('Could not determine home directory')
}
var shop_path = home + '/.cell'
var packages_path = shop_path + '/packages'
var core_path = packages_path + '/core'
if (!fd.is_dir(core_path)) {
throw new Error('Cell shop not found at ' + shop_path + '. Run "cell install" to set up.')
}
var use_cache = {}
use_cache['core/os'] = os
// Load a core module from the file system
function use_core(path) {
var cache_path = `2::${path}`;
if (use_cache[cache_path])
return use_cache[cache_path];
var cache_key = 'core/' + path
if (use_cache[cache_key])
return use_cache[cache_key];
var sym = use_embed(path.replace('/','_'))
// Core scripts are now in .cell/core/scripts
// Core scripts are in packages/core/
var file_path = core_path + '/' + path + MOD_EXT
if (fd.is_file(file_path)) {
var script_blob = fd.slurp(file_path)
var script = utf8.decode(script_blob)
var mod = `(function setup_module($_){${script}})`
var fn = js.eval(path, mod)
var fn = js.eval('core:' + path, mod)
var result = fn.call(sym);
use_cache[cache_path] = result;
use_cache[cache_key] = result;
return result;
}
use_cache[cache_path] = sym;
use_cache[cache_key] = sym;
return sym;
}
@@ -205,47 +213,27 @@ function create_actor(desc = {id:guid()}) {
var $_ = create_actor()
var shop = use('shop')
os.use_cache = use_cache
shop.set_os(os, $_)
os.global_shop_path = shop_path
var shop = use('shop')
globalThis.use = shop.use
var config = shop.load_config()
// Get package name from a resolved path
function get_package_from_path(path) {
if (!path) return null
var modules_prefix = shop.get_shop_path() + '/modules/'
if (path.startsWith(modules_prefix)) {
var rest = path.substring(modules_prefix.length)
var slash_idx = rest.indexOf('/')
if (slash_idx > 0) {
return rest.substring(0, slash_idx)
}
}
return null
}
globalThis.json = use('json')
var time = use('time')
var default_config = {
var config = {
ar_timer: 60,
actor_memory:0,
net_service:0.1,
reply_timeout:60,
main: false,
main: true
}
config ??= {}
config.system ??= {}
config.system.__proto__ = default_config
cell.config = config
ENETSERVICE = config.system.net_service
REPLYTIMEOUT = config.system.reply_timeout
ENETSERVICE = config.net_service
REPLYTIMEOUT = config.reply_timeout
/*
When handling a message, the message appears like this:
@@ -609,12 +597,12 @@ function turn(msg)
//log.console(`FIXME: need to get main from config, not just set to true`)
//log.console(`FIXME: remove global access (ie globalThis.use)`)
//log.console(`FIXME: add freeze/unfreeze at this level, so we can do it (but scripts cannot)`)
actor_mod.register_actor(cell.id, turn, true, config.system.ar_timer)
actor_mod.register_actor(cell.id, turn, true, config.ar_timer)
if (config.system.actor_memory)
js.mem_limit(config.system.actor_memory)
if (config.actor_memory)
js.mem_limit(config.actor_memory)
if (config.system.stack_max)
if (config.stack_max)
js.max_stacksize(config.system.stack_max);
overling = cell.args.overling
@@ -648,21 +636,6 @@ if (!program) {
os.exit(1)
}
// Find the package containing the program
// The program path is resolved relative to current directory
// Find the package containing the program
// The program path is resolved relative to current directory
// var package_dir = shop.set_current_package(program)
// if (package_dir) {
// // Reload config from the package
// config = shop.load_config()
// if (config) {
// config.system = config.system || {}
// config.system.__proto__ = default_config
// cell.config = config
// }
// }
function handle_actor_disconnect(id) {
var greeter = greeters[id]
if (greeter) {
@@ -751,9 +724,15 @@ actor_mod.setname(cell.args.program)
var prog = cell.args.program
// Resolve the main program path
var package = use('package')
var locator = shop.resolve_locator(cell.args.program + ".ce", null)
if (!locator) {
var pkg = package.find_package_dir(cell.args.program + ".ce")
locator = shop.resolve_locator(cell.args.program + ".ce", pkg)
}
if (!locator)
throw new Error(`Main program ${cell.args.program} could not be found`)

18
list.ce
View File

@@ -4,6 +4,7 @@
// cell list package <name> -> list the packages for the package <name>
var shop = use('shop')
var pkg = use('package')
var mode = 'local'
var target_pkg = null
@@ -56,23 +57,16 @@ if (mode == 'local') {
if (all.length == 0) log.console(" (none)")
} else if (mode == 'shop') {
log.console("Shop Packages:")
var all = shop.list_shop_packages()
// Sort by package name or something
var all = shop.list_packages()
if (all.length == 0) {
if (all.length == 0)
log.console(" (none)")
} else {
for (var i = 0; i < all.length; i++) {
var item = all[i]
var name = item.package || "unknown"
var ver = item.commit || item.type || "unknown"
log.console(" " + name + " [" + ver + "]")
}
}
else
all.forEach(package => log.console(" " + package))
}
function print_deps(ctx) {
var deps = shop.dependencies(ctx)
var deps = pkg.dependencies(ctx)
var aliases = []
for (var k in deps) aliases.push(k)
aliases.sort()

32
ls.ce
View File

@@ -3,26 +3,15 @@
// otherwise, list the local one
var shop = use('shop')
var package = use('package')
var ctx = null
var pkg_name = "Local"
if (args && args.length > 0) {
var alias = args[0]
ctx = shop.get_normalized_module(alias, null)
if (!ctx) {
log.console("Package '" + alias + "' not found in dependencies.")
$_.stop()
return
}
pkg_name = alias + " (" + ctx + ")"
}
var modules = shop.list_modules(ctx)
log.console("Modules in " + pkg_name + ":")
var pkg = args[0] || package.find_package_dir('.')
var modules = package.list_modules(pkg)
var programs = package.list_programs(pkg)
log.console("Modules in " + pkg + ":")
modules.sort()
if (modules.length == 0) {
log.console(" (none)")
} else {
@@ -31,4 +20,15 @@ if (modules.length == 0) {
}
}
log.console("")
log.console("Programs in " + pkg + ":")
programs.sort()
if (programs.length == 0) {
log.console(" (none)")
} else {
for (var i = 0; i < programs.length; i++) {
log.console(" " + programs[i])
}
}
$_.stop()

123
pack.ce Normal file
View File

@@ -0,0 +1,123 @@
// cell pack <package> [options] - Build static binary for a package and its dependencies
//
// Usage:
// cell pack <package> Build static binary for package (output: app)
// cell pack <package> -o <name> Specify output name
// cell pack <package> -t <target> Cross-compile for target platform
var build = use('build')
var shop = use('shop')
var pkg_tools = use('package')
var target = null
var output_name = 'app'
var target_package = null
var buildtype = 'release'
if (args.length < 1) {
log.error('Usage: cell pack <package> [options]')
log.error('')
log.error('Options:')
log.error(' -o, --output <name> Output name for binary (default: app)')
log.error(' -t, --target <target> Cross-compile for target platform')
log.error(' -b, --buildtype <type> Build type: release, debug, minsize (default: release)')
log.error('')
log.error('Available targets: ' + build.list_targets().join(', '))
$_.stop()
return
}
target_package = args[0]
for (var i = 1; i < args.length; i++) {
if (args[i] == '-t' || args[i] == '--target') {
if (i + 1 < args.length) {
target = args[++i]
} else {
log.error('-t requires a target')
$_.stop()
}
} else if (args[i] == '-o' || args[i] == '--output') {
if (i + 1 < args.length) {
output_name = args[++i]
} else {
log.error('-o requires an output name')
$_.stop()
}
} else if (args[i] == '-b' || args[i] == '--buildtype') {
if (i + 1 < args.length) {
buildtype = args[++i]
if (buildtype != 'release' && buildtype != 'debug' && buildtype != 'minsize') {
log.error('Invalid buildtype: ' + buildtype + '. Must be release, debug, or minsize')
$_.stop()
}
} else {
log.error('-b requires a buildtype (release, debug, minsize)')
$_.stop()
}
} else if (args[i] == '-h' || args[i] == '--help') {
log.console('Usage: cell pack <package> [options]')
log.console('')
log.console('Options:')
log.console(' -o, --output <name> Output name for binary (default: app)')
log.console(' -t, --target <target> Cross-compile for target platform')
log.console(' -b, --buildtype <type> Build type: release, debug, minsize (default: release)')
log.console('')
log.console('Available targets: ' + build.list_targets().join(', '))
$_.stop()
} else {
log.error('Unknown option: ' + args[i])
$_.stop()
}
}
// Detect target if not specified
if (!target) {
target = build.detect_host_target()
if (target) log.console('Target: ' + target)
}
if (target && !build.has_target(target)) {
log.error('Invalid target: ' + target)
log.console('Available targets: ' + build.list_targets().join(', '))
$_.stop()
}
// Prepare packages: core + dependencies + target package
var packages = ['core']
var deps = pkg_tools.gather_dependencies(target_package)
for (var i = 0; i < deps.length; i++) {
packages.push(deps[i])
}
packages.push(target_package)
// Remove duplicates
var unique_packages = []
var seen = {}
for (var i = 0; i < packages.length; i++) {
if (!seen[packages[i]]) {
seen[packages[i]] = true
unique_packages.push(packages[i])
}
}
packages = unique_packages
log.console('Preparing packages...')
for (var package of packages) {
if (package == 'core') continue
shop.extract(package)
}
log.console('Building static binary from ' + text(packages.length) + ' packages: ' + packages.join(', '))
try {
var result = build.build_static(packages, target, output_name, buildtype)
log.console('Build complete: ' + result)
} catch (e) {
log.error('Build failed: ')
log.error(e)
$_.stop()
}
$_.stop()

305
package.cm Normal file
View File

@@ -0,0 +1,305 @@
var package = {}
var fd = use('fd')
var toml = use('toml')
var os = use('os')
function get_path(name)
{
return os.global_shop_path + '/packages/' + name
}
package.load_config = function(name)
{
var config_path = get_path(name) + '/cell.toml'
if (!fd.is_file(config_path))
throw new Error(`${config_path} isn't a path`)
return toml.decode(text(fd.slurp(config_path)))
}
package.save_config = function(name, config)
{
var config_path = get_path(name) + '/cell.toml'
fd.slurpwrite(config_path, utf8.encode(toml.encode(config)))
}
package.dependencies = function(name)
{
return package.load_config(name).dependencies
}
package.find_alias = function(name, locator)
{
var config = package.load_config(name)
if (!config.dependencies) return null
for (var alias in config.dependencies)
if (config.dependencies[alias] == locator)
return alias
return null
}
// alias is optional
package.add_dependency = function(name, locator, alias = locator)
{
var config = package.load_config(name)
if (!config.dependencies) config.dependencies = {}
config.dependencies[alias] = locator
package.save_config(name, config)
}
// locator can be a locator or alias
package.remove_dependency = function(name, locator)
{
var config = package.load_config(name)
if (!config.dependencies) return
if (config.dependencies[locator])
delete config.dependencies[locator]
else {
var alias = package.find_alias(name, locator)
if (alias)
delete config.dependencies[alias]
}
package.save_config(name, config)
}
package.find_package_dir = function(file)
{
var absolute = fd.realpath(file)
var dir = absolute
if (fd.is_file(dir)) {
var last_slash = dir.lastIndexOf('/')
if (last_slash > 0) dir = dir.substring(0, last_slash)
}
while (dir && dir.length > 0) {
var toml_path = dir + '/cell.toml'
if (fd.is_file(toml_path)) {
return dir
}
var last_slash = dir.lastIndexOf('/')
if (last_slash <= 0) break
dir = dir.substring(0, last_slash)
}
return null
}
// For a given package,
// checks for an alias in path, and returns
// { package, path }
// so package + path is the full path
// Returns null if no alias is found for the given path
package.split_alias = function(name, path)
{
if (!path || path.length == 0) {
return null
}
var parts = path.split('/')
var first_part = parts[0]
var config = package.load_config(name)
if (config.dependencies && config.dependencies[first_part]) {
var dep_locator = config.dependencies[first_part]
var remaining_path = parts.slice(1).join('/')
return { package: dep_locator, path: remaining_path }
}
return null
}
package.gather_dependencies = function(name)
{
var all_deps = {}
var visited = {}
function gather_recursive(pkg_name) {
if (visited[pkg_name]) return
visited[pkg_name] = true
var deps = package.dependencies(pkg_name)
if (!deps) return
for (var alias in deps) {
var locator = deps[alias]
if (!all_deps[locator]) {
all_deps[locator] = true
gather_recursive(locator)
}
}
}
gather_recursive(name)
return Object.keys(all_deps)
}
package.list_files = function(pkg) {
var dir = get_path(pkg)
var files = []
var walk = function(current_dir, current_prefix) {
var list = fd.readdir(current_dir)
if (!list) return
for (var i = 0; i < list.length; i++) {
var item = list[i]
if (item == '.' || item == '..') continue
if (item.startsWith('.')) continue
// Skip build directories in root
var full_path = current_dir + "/" + item
var rel_path = current_prefix ? current_prefix + "/" + item : item
var st = fd.stat(full_path)
if (st.isDirectory) {
walk(full_path, rel_path)
} else {
files.push(rel_path)
}
}
}
if (fd.is_dir(dir)) {
walk(dir, "")
}
return files
}
package.list_modules = function(name) {
var files = package.list_files(name)
var modules = []
for (var i = 0; i < files.length; i++) {
if (files[i].endsWith('.cm')) {
modules.push(files[i].substring(0, files[i].length - 3))
}
}
return modules
}
package.list_programs = function(name) {
var files = package.list_files(name)
var programs = []
for (var i = 0; i < files.length; i++) {
if (files[i].endsWith('.ce')) {
programs.push(files[i].substring(0, files[i].length - 3))
}
}
return programs
}
// Get flags from cell.toml for a package
// flag_type is 'CFLAGS' or 'LDFLAGS'
// target is optional (e.g., 'macos_arm64', 'playdate')
// Returns an array of flag strings
package.get_flags = function(name, flag_type, target) {
var config = package.load_config(name)
var flags = []
// Base flags
if (config.compilation && config.compilation[flag_type]) {
var base = config.compilation[flag_type]
flags = flags.concat(base.split(/\s+/).filter(function(f) { return f.length > 0 }))
}
// Target-specific flags
if (target && config.compilation && config.compilation[target] && config.compilation[target][flag_type]) {
var target_flags = config.compilation[target][flag_type]
flags = flags.concat(target_flags.split(/\s+/).filter(function(f) { return f.length > 0 }))
}
return flags
}
// Get all C files for a package, handling target-specific variants
// Excludes main.c for dynamic builds (when exclude_main is true)
// Handles patterns like fd.c vs fd_playdate.c
package.get_c_files = function(name, target, exclude_main) {
var toolchains = use('toolchains')
var known_targets = Object.keys(toolchains)
var files = package.list_files(name)
// Group files by their base name (without target suffix)
var groups = {} // base_key -> { generic: file, variants: { target: file } }
for (var i = 0; i < files.length; i++) {
var file = files[i]
if (!file.endsWith('.c') && !file.endsWith('.cpp')) continue
var ext = file.endsWith('.cpp') ? '.cpp' : '.c'
var base = file.substring(0, file.length - ext.length)
var dir = ''
var name_part = base
var slash = base.lastIndexOf('/')
if (slash >= 0) {
dir = base.substring(0, slash + 1)
name_part = base.substring(slash + 1)
}
// Check for target suffix
var is_variant = false
var variant_target = null
var generic_name = name_part
for (var t = 0; t < known_targets.length; t++) {
var suffix = '_' + known_targets[t]
if (name_part.endsWith(suffix)) {
is_variant = true
variant_target = known_targets[t]
generic_name = name_part.substring(0, name_part.length - suffix.length)
break
}
}
var group_key = dir + generic_name + ext
if (!groups[group_key]) {
groups[group_key] = { generic: null, variants: {} }
}
if (is_variant) {
groups[group_key].variants[variant_target] = file
} else {
groups[group_key].generic = file
}
}
// Select appropriate file from each group
var result = []
for (var key in groups) {
var group = groups[key]
var selected = null
// Prefer target-specific variant if available
if (target && group.variants[target]) {
selected = group.variants[target]
} else if (group.generic) {
selected = group.generic
}
if (selected) {
// Skip main.c if requested
if (exclude_main) {
var basename = selected
var s = selected.lastIndexOf('/')
if (s >= 0) basename = selected.substring(s + 1)
if (basename == 'main.c' || basename.startsWith('main_')) continue
}
result.push(selected)
}
}
return result
}
// Get the absolute path for a package
package.get_dir = function(name) {
return get_path(name)
}
return package

View File

@@ -1,29 +0,0 @@
// cell replace [package] [path]
// replace a package with a local directory
var shop = use('shop')
if (args.length < 2) {
log.console("Usage: cell replace <package> <path>")
log.console(" cell replace <package> --remove")
$_.stop()
}
var pkg = args[0]
var path = args[1]
if (path == '--remove') {
if (shop.remove_replacement(pkg)) {
log.console("Replacement removed.")
} else {
log.console("Failed to remove replacement.")
}
} else {
if (shop.add_replacement(pkg, path)) {
log.console("Replacement added.")
} else {
log.console("Failed to add replacement.")
}
}
$_.stop()

1896
shop.cm

File diff suppressed because it is too large Load Diff

234
toolchains.cm Normal file
View File

@@ -0,0 +1,234 @@
return {
playdate: {
c: 'arm-none-eabi-gcc',
cpp: 'arm-none-eabi-g++',
ar: 'arm-none-eabi-ar',
strip: 'arm-none-eabi-strip',
objcopy: 'arm-none-eabi-objcopy',
ld: 'arm-none-eabi-gcc',
system: 'playdate',
cpu_family: 'arm',
cpu: 'cortex-m7',
endian: 'little',
c_args: ['-mcpu=cortex-m7', '-mthumb', '-mfloat-abi=hard', '-mfpu=fpv5-sp-d16', '-fno-exceptions'],
c_link_args: ['-mcpu=cortex-m7', '-mthumb', '-mfloat-abi=hard', '-mfpu=fpv5-sp-d16', '-nostartfiles', "-T/Users/john/Developer/PlaydateSDK/C_API/buildsupport/link_map.ld"]
},
windows: {
c: 'x86_64-w64-mingw32-gcc',
cpp: 'x86_64-w64-mingw32-g++',
ar: 'x86_64-w64-mingw32-ar',
windres: 'x86_64-w64-mingw32-windres',
strip: 'x86_64-w64-mingw32-strip',
system: 'windows',
cpu_family: 'x86_64',
cpu: 'x86_64',
endian: 'little',
c_args: [],
c_link_args: []
},
windows_i686: {
c: 'i686-w64-mingw32-gcc',
cpp: 'i686-w64-mingw32-g++',
ar: 'i686-w64-mingw32-ar',
windres: 'i686-w64-mingw32-windres',
strip: 'i686-w64-mingw32-strip',
system: 'windows',
cpu_family: 'x86',
cpu: 'i686',
endian: 'little',
c_args: [],
c_link_args: []
},
linux: {
c: 'zig cc -target x86_64-linux-musl',
cpp: 'zig c++ -target x86_64-linux-musl',
ar: 'zig ar',
strip: 'strip',
system: 'linux',
cpu_family: 'x86_64',
cpu: 'x86_64',
endian: 'little',
c_args: [],
c_link_args: []
},
linux_arm64: {
c: 'zig cc -target aarch64-linux-musl',
cpp: 'zig c++ -target aarch64-linux-musl',
ar: 'zig ar',
strip: 'strip',
system: 'linux',
cpu_family: 'aarch64',
cpu: 'aarch64',
endian: 'little',
c_args: [],
c_link_args: []
},
macos_arm64: {
c: 'clang -target arm64-apple-macos11',
cpp: 'clang++ -target arm64-apple-macos11',
ar: 'ar',
strip: 'strip',
system: 'darwin',
cpu_family: 'aarch64',
cpu: 'aarch64',
endian: 'little',
c_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk'],
c_link_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk']
},
macos_x86_64: {
c: 'clang -target x86_64-apple-macos10.12',
cpp: 'clang++ -target x86_64-apple-macos10.12',
ar: 'ar',
strip: 'strip',
system: 'darwin',
cpu_family: 'x86_64',
cpu: 'x86_64',
endian: 'little',
c_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk'],
c_link_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk']
},
ios_arm64: {
c: 'clang -target arm64-apple-ios12.0',
cpp: 'clang++ -target arm64-apple-ios12.0',
ar: 'ar',
strip: 'strip',
system: 'ios',
cpu_family: 'aarch64',
cpu: 'aarch64',
endian: 'little',
c_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk', '-fembed-bitcode'],
c_link_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk', '-fembed-bitcode']
},
ios_simulator_arm64: {
c: 'clang -target arm64-apple-ios12.0-simulator',
cpp: 'clang++ -target arm64-apple-ios12.0-simulator',
ar: 'ar',
strip: 'strip',
system: 'ios',
cpu_family: 'aarch64',
cpu: 'aarch64',
endian: 'little',
c_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk'],
c_link_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk']
},
ios_simulator_x86_64: {
c: 'clang -target x86_64-apple-ios12.0-simulator',
cpp: 'clang++ -target x86_64-apple-ios12.0-simulator',
ar: 'ar',
strip: 'strip',
system: 'ios',
cpu_family: 'x86_64',
cpu: 'x86_64',
endian: 'little',
c_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk'],
c_link_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk']
},
tvos_arm64: {
c: 'clang -target arm64-apple-tvos12.0',
cpp: 'clang++ -target arm64-apple-tvos12.0',
ar: 'ar',
strip: 'strip',
system: 'tvos',
cpu_family: 'aarch64',
cpu: 'aarch64',
endian: 'little',
c_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk', '-fembed-bitcode'],
c_link_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk', '-fembed-bitcode']
},
tvos_simulator_arm64: {
c: 'clang -target arm64-apple-tvos12.0-simulator',
cpp: 'clang++ -target arm64-apple-tvos12.0-simulator',
ar: 'ar',
strip: 'strip',
system: 'tvos',
cpu_family: 'aarch64',
cpu: 'aarch64',
endian: 'little',
c_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVSimulator.platform/Developer/SDKs/AppleTVSimulator.sdk'],
c_link_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVSimulator.platform/Developer/SDKs/AppleTVSimulator.sdk']
},
tvos_simulator_x86_64: {
c: 'clang -target x86_64-apple-tvos12.0-simulator',
cpp: 'clang++ -target x86_64-apple-tvos12.0-simulator',
ar: 'ar',
strip: 'strip',
system: 'tvos',
cpu_family: 'x86_64',
cpu: 'x86_64',
endian: 'little',
c_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVSimulator.platform/Developer/SDKs/AppleTVSimulator.sdk'],
c_link_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVSimulator.platform/Developer/SDKs/AppleTVSimulator.sdk']
},
watchos_arm64: {
c: 'clang -target arm64_32-apple-watchos5.0',
cpp: 'clang++ -target arm64_32-apple-watchos5.0',
ar: 'ar',
strip: 'strip',
system: 'watchos',
cpu_family: 'aarch64',
cpu: 'arm64_32',
endian: 'little',
c_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk', '-fembed-bitcode'],
c_link_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk', '-fembed-bitcode']
},
watchos_simulator_arm64: {
c: 'clang -target arm64-apple-watchos5.0-simulator',
cpp: 'clang++ -target arm64-apple-watchos5.0-simulator',
ar: 'ar',
strip: 'strip',
system: 'watchos',
cpu_family: 'aarch64',
cpu: 'aarch64',
endian: 'little',
c_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/WatchSimulator.platform/Developer/SDKs/WatchSimulator.sdk'],
c_link_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/WatchSimulator.platform/Developer/SDKs/WatchSimulator.sdk']
},
watchos_simulator_x86_64: {
c: 'clang -target x86_64-apple-watchos5.0-simulator',
cpp: 'clang++ -target x86_64-apple-watchos5.0-simulator',
ar: 'ar',
strip: 'strip',
system: 'watchos',
cpu_family: 'x86_64',
cpu: 'x86_64',
endian: 'little',
c_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/WatchSimulator.platform/Developer/SDKs/WatchSimulator.sdk'],
c_link_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/WatchSimulator.platform/Developer/SDKs/WatchSimulator.sdk']
},
visionos_arm64: {
c: 'clang -target arm64-apple-xros1.0',
cpp: 'clang++ -target arm64-apple-xros1.0',
ar: 'ar',
strip: 'strip',
system: 'visionos',
cpu_family: 'aarch64',
cpu: 'aarch64',
endian: 'little',
c_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/XROS.platform/Developer/SDKs/XROS.sdk'],
c_link_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/XROS.platform/Developer/SDKs/XROS.sdk']
},
visionos_simulator_arm64: {
c: 'clang -target arm64-apple-xros1.0-simulator',
cpp: 'clang++ -target arm64-apple-xros1.0-simulator',
ar: 'ar',
strip: 'strip',
system: 'visionos',
cpu_family: 'aarch64',
cpu: 'aarch64',
endian: 'little',
c_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/XRSimulator.platform/Developer/SDKs/XRSimulator.sdk'],
c_link_args: ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/XRSimulator.platform/Developer/SDKs/XRSimulator.sdk']
},
emscripten: {
c: 'emcc',
cpp: 'em++',
ar: 'emar',
strip: 'emstrip',
system: 'emscripten',
cpu_family: 'wasm32',
cpu: 'wasm32',
endian: 'little',
c_args: [],
c_link_args: []
}
}

View File

@@ -1,45 +1,77 @@
// cell update [alias] - Update packages to latest versions and rebuild dynamic libraries
// cell update - Update packages from remote sources
//
// This command:
// 1. Updates all packages from their remote sources
// 2. Rebuilds all dynamic libraries via 'cell build -d'
// This command checks for updates to all packages and downloads new versions.
// For local packages, ensures the symlink is correct.
// For remote packages, checks the remote for new commits.
//
// Usage:
// cell update - Update all packages
// cell update <package> - Update a specific package
var shop = use('shop')
var os = use('os')
var target = null
var target_pkg = 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.")
if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell update [package]")
log.console("Update packages from remote sources.")
log.console("")
log.console("Options:")
log.console(" --target, -t <target> Build for specific target platform")
log.console("Arguments:")
log.console(" package Optional package name to update. If omitted, updates all.")
log.console("")
log.console("This command updates all installed packages from their remote")
log.console("sources, then rebuilds all dynamic libraries.")
log.console("This command checks for updates to all packages and downloads")
log.console("new versions. For local packages, ensures the symlink is correct.")
$_.stop()
} else if (!args[i].startsWith('-')) {
target_pkg = args[i]
}
}
var packages = shop.list_shop_packages()
log.console("Checking for updates (" + text(packages.length) + " packages)...")
// 1. Update all packages
for (var info of packages) {
var pack = info.package
if (!pack || pack == 'core') continue
function update_and_fetch(pkg)
{
var lock = shop.load_lock()
var old_entry = lock[pkg]
var old_commit = old_entry ? old_entry.commit : null
log.console("Updating " + pack)
shop.update(pack)
var new_entry = shop.update(pkg)
if (new_entry && new_entry.commit) {
log.console(" " + pkg + " " + old_commit.substring(0, 8) + " -> " + new_entry.commit.substring(0, 8))
shop.fetch(pkg)
shop.build_package_scripts(pkg)
return true
}
return false
}
if (target_pkg) {
if (update_and_fetch(target_pkg))
log.console("Updated " + target_pkg + ".")
else
log.console(target_pkg + " is up to date.")
} else {
var packages = shop.list_packages()
var pkg_count = packages.length
log.console("Checking for updates (" + text(pkg_count) + " package" + (pkg_count == 1 ? "" : "s") + ")...")
var updated_count = 0
for (var i = 0; i < packages.length; i++) {
var pkg = packages[i]
if (pkg == 'core') continue
if (update_and_fetch(pkg)) {
updated_count++
}
}
if (updated_count > 0) {
log.console("Updated " + text(updated_count) + " package" + (updated_count == 1 ? "" : "s") + ".")
} else {
log.console("All packages are up to date.")
}
}
$_.stop()

3
why.ce
View File

@@ -1,4 +1,5 @@
var shop = use('shop')
var pkg = use('package')
if (!args || args.length < 1) {
log.console("Usage: cell why <package>")
@@ -19,7 +20,7 @@ var found = false
// stack: array of {alias, pkg} leading to current_pkg
function search(current_pkg, stack) {
var deps = shop.dependencies(current_pkg)
var deps = pkg.dependencies(current_pkg)
// Sort for consistent output
var aliases = []