global cell shops
This commit is contained in:
52
Makefile
52
Makefile
@@ -3,54 +3,72 @@
|
||||
#
|
||||
# If cell doesn't exist yet, use 'make bootstrap' first (requires meson)
|
||||
# or manually build with meson once.
|
||||
#
|
||||
# The cell shop is at ~/.cell and core scripts are installed to ~/.cell/core
|
||||
|
||||
cell: libcell_runtime.dylib cell_main core.qop
|
||||
cat cell_main > cell
|
||||
cat core.qop >> cell
|
||||
CELL_SHOP = $(HOME)/.cell
|
||||
CELL_CORE = $(CELL_SHOP)/core
|
||||
|
||||
cell: libcell_runtime.dylib cell_main
|
||||
cp cell_main cell
|
||||
chmod +x cell
|
||||
cp cell /opt/homebrew/bin/cell
|
||||
cp libcell_runtime.dylib /opt/homebrew/lib/
|
||||
|
||||
# Build the shared runtime library (everything except main.c)
|
||||
# Uses existing cell to run build -d
|
||||
libcell_runtime.dylib: .cell/build/dynamic
|
||||
libcell_runtime.dylib: $(CELL_SHOP)/build/dynamic
|
||||
cell build -d
|
||||
cp .cell/build/dynamic/libcell_runtime.dylib .
|
||||
cp $(CELL_SHOP)/build/dynamic/libcell_runtime.dylib .
|
||||
|
||||
# Build the thin main wrapper that links to libcell_runtime
|
||||
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
|
||||
|
||||
# Create core.qop from scripts folder
|
||||
core.qop: scripts/*.cm scripts/*.c
|
||||
cell qopconv -d scripts . core.qop
|
||||
# Install core: symlink this directory to ~/.cell/core
|
||||
install: $(CELL_SHOP)
|
||||
@echo "Linking cell core to $(CELL_CORE)"
|
||||
rm -rf $(CELL_CORE)
|
||||
ln -s $(PWD) $(CELL_CORE)
|
||||
@echo "Core installed."
|
||||
|
||||
# Create the cell shop directories
|
||||
$(CELL_SHOP):
|
||||
mkdir -p $(CELL_SHOP)
|
||||
mkdir -p $(CELL_SHOP)/packages
|
||||
mkdir -p $(CELL_SHOP)/cache
|
||||
mkdir -p $(CELL_SHOP)/build
|
||||
|
||||
$(CELL_CORE):
|
||||
ln -s $(PWD) $(CELL_CORE)
|
||||
|
||||
# Static build: creates a fully static cell binary (for distribution)
|
||||
static:
|
||||
cell build
|
||||
cp .cell/build/static/cell .
|
||||
cat core.qop >> cell
|
||||
cp $(CELL_SHOP)/build/static/cell .
|
||||
|
||||
# Bootstrap: build cell from scratch using meson (only needed once)
|
||||
bootstrap:
|
||||
# Also installs core scripts to ~/.cell/core
|
||||
bootstrap: install
|
||||
meson setup build_bootstrap -Dbuildtype=release
|
||||
meson compile -C build_bootstrap
|
||||
cp build_bootstrap/cell .
|
||||
cp build_bootstrap/libcell_runtime.dylib .
|
||||
@echo "Bootstrap complete. Now run 'make' to rebuild with cell itself."
|
||||
@echo "Bootstrap complete. Cell shop initialized at $(CELL_SHOP)"
|
||||
@echo "Now run 'make' to rebuild with cell itself."
|
||||
|
||||
# Clean build artifacts
|
||||
clean:
|
||||
rm -rf .cell/build build_bootstrap
|
||||
rm -f cell cell_main libcell_runtime.dylib core.qop
|
||||
rm -rf $(CELL_SHOP)/build build_bootstrap
|
||||
rm -f cell cell_main libcell_runtime.dylib
|
||||
|
||||
# Ensure dynamic build directory exists
|
||||
.cell/build/dynamic:
|
||||
mkdir -p .cell/build/dynamic
|
||||
$(CELL_SHOP)/build/dynamic: $(CELL_SHOP)
|
||||
mkdir -p $(CELL_SHOP)/build/dynamic
|
||||
|
||||
# Legacy meson target
|
||||
meson:
|
||||
meson setup build_dbg -Dbuildtype=debugoptimized
|
||||
meson install -C build_dbg
|
||||
|
||||
.PHONY: cell static bootstrap clean meson
|
||||
.PHONY: cell static bootstrap clean meson install
|
||||
|
||||
@@ -89,13 +89,20 @@ function find_cell_dir() {
|
||||
if (config && config.dependencies && config.dependencies.cell) {
|
||||
var pkg = config.dependencies.cell
|
||||
var parsed = shop.parse_package(pkg)
|
||||
var pkg_dir = '.cell/modules/' + parsed.path
|
||||
var pkg_dir = shop.get_shop_path() + '/modules/' + 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')) {
|
||||
@@ -139,7 +146,7 @@ if (dynamic_mode) {
|
||||
log.console("Found " + text(c_files.length) + " C files to compile")
|
||||
|
||||
// Get build directory
|
||||
var build_dir = dynamic_mode ? '.cell/build/dynamic' : build.get_build_dir(target)
|
||||
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
|
||||
@@ -159,8 +166,9 @@ if (dynamic_mode) {
|
||||
var compile_options = {
|
||||
target: target,
|
||||
cflags: cflags,
|
||||
includes: [],
|
||||
defines: {}
|
||||
includes: [cell_dir, cell_dir + '/source'], // Add cell root and source for includes
|
||||
defines: {},
|
||||
module_dir: cell_dir // Ensure we run compilation from cell dir context
|
||||
}
|
||||
|
||||
// Add target-specific defines
|
||||
@@ -171,13 +179,33 @@ if (target == 'playdate') {
|
||||
// Compile all C files
|
||||
var objects = []
|
||||
for (var i = 0; i < c_files.length; i++) {
|
||||
var src = cell_dir + '/' + c_files[i]
|
||||
var obj = build_dir + '/' + c_files[i] + '.o'
|
||||
var src = c_files[i] // Relative to module_dir because we set it above?
|
||||
// No, c_files list was created with full paths (or relative to cell_dir?)
|
||||
// build.list_files returns relative paths if used with prefix.
|
||||
// We collected them as 'source/file.c'.
|
||||
// But compile_file expects src_path. If we use module_dir, src_path should be relative to it.
|
||||
|
||||
// Adjusted:
|
||||
var src_rel = c_files[i]
|
||||
var obj_base = shop.get_global_build_dir() + '/cell' // Build cell into its own area?
|
||||
// Wait, existing logic was:
|
||||
// var build_dir = dynamic_mode ? shop.get_shop_path() + '/build/dynamic' : build.get_build_dir(target)
|
||||
|
||||
// Let's stick to existing obj path logic but ensure src is handled right.
|
||||
// construct object path
|
||||
var obj = build_dir + (dynamic_mode ? '/' : '/static/') + src_rel + '.o' // Separate static build
|
||||
|
||||
// The 'src' var in loop was: var src = cell_dir + '/' + c_files[i]
|
||||
// But if we pass module_dir = cell_dir, we should pass src relative to it?
|
||||
// build.cm says: if (src_path.startsWith(module_dir + '/')) src_file = src_path.substring...
|
||||
|
||||
// So we can pass absolute path, it should handle it.
|
||||
var src_abs = cell_dir + '/' + src_rel
|
||||
|
||||
// Check if recompilation needed
|
||||
var needs_compile = true
|
||||
if (fd.is_file(obj)) {
|
||||
var src_stat = fd.stat(src)
|
||||
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
|
||||
@@ -185,7 +213,9 @@ for (var i = 0; i < c_files.length; i++) {
|
||||
}
|
||||
|
||||
if (needs_compile) {
|
||||
var result = build.compile_file(src, obj, compile_options)
|
||||
// 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")
|
||||
$_.stop()
|
||||
@@ -200,7 +230,7 @@ if (!dynamic_mode) {
|
||||
for (var p = 0; p < packages.length; p++) {
|
||||
var pkg = packages[p]
|
||||
var parsed = shop.parse_package(pkg)
|
||||
var pkg_dir = '.cell/modules/' + parsed.path
|
||||
var pkg_dir = shop.get_shop_path() + '/modules/' + parsed.path
|
||||
|
||||
if (!fd.is_dir(pkg_dir)) continue
|
||||
|
||||
@@ -224,7 +254,10 @@ if (!dynamic_mode) {
|
||||
|
||||
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'
|
||||
|
||||
// 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 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'
|
||||
@@ -392,7 +425,7 @@ if (dynamic_mode) {
|
||||
for (var p = 0; p < packages.length; p++) {
|
||||
var pkg = packages[p]
|
||||
var parsed = shop.parse_package(pkg)
|
||||
var pkg_dir = '.cell/modules/' + parsed.path
|
||||
var pkg_dir = shop.get_shop_path() + '/modules/' + 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]
|
||||
|
||||
@@ -8,6 +8,7 @@ var os_mod = use('os')
|
||||
|
||||
var Build = {}
|
||||
// Embedded cross-compilation toolchain configurations
|
||||
// Note: build.cm uses shop to resolve paths
|
||||
Build.toolchains = {
|
||||
playdate: {
|
||||
binaries: {
|
||||
@@ -384,6 +385,10 @@ function ensure_dir(path) {
|
||||
if (fd.stat(path).isDirectory) return true
|
||||
var parts = path.split('/')
|
||||
var current = ''
|
||||
// Handle absolute paths (leading /)
|
||||
if (path.startsWith('/')) {
|
||||
current = '/'
|
||||
}
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
if (parts[i] == '') continue
|
||||
current += parts[i] + '/'
|
||||
@@ -530,9 +535,12 @@ Build.select_c_files = function(files, target) {
|
||||
}
|
||||
|
||||
// Get build directory for a target
|
||||
// Uses shop module to get the global shop path
|
||||
Build.get_build_dir = function(target) {
|
||||
if (!target) return '.cell/build/static'
|
||||
return '.cell/build/' + target
|
||||
var shop = use('shop')
|
||||
var shop_path = shop.get_shop_path()
|
||||
if (!target) return shop_path + '/build/static'
|
||||
return shop_path + '/build/' + target
|
||||
}
|
||||
|
||||
// Compile a single C file
|
||||
@@ -580,10 +588,14 @@ Build.compile_file = function(src_path, obj_path, options) {
|
||||
|
||||
// Adjust source path relative to module dir
|
||||
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)
|
||||
} 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
|
||||
}
|
||||
|
||||
// Adjust output path to be absolute/relative to HERE
|
||||
@@ -592,6 +604,12 @@ Build.compile_file = function(src_path, obj_path, options) {
|
||||
out_file = '"$HERE/' + out_file + '"'
|
||||
}
|
||||
|
||||
// If we're changing CWD to module_dir, we need to make sure out_file (if absolute) is still valid?
|
||||
// Yes absolute paths work anywhere.
|
||||
// Issue reported was: /Users/john/work/accio//Users/john/.cell/build/...
|
||||
// This happens if we constructed a path that was "module_dir + '/' + absolute_path" somewhere.
|
||||
// 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
|
||||
} else {
|
||||
@@ -703,8 +721,9 @@ Build.get_flags = function(config, platform, key) {
|
||||
}
|
||||
|
||||
// Load config from a directory
|
||||
// Config is now at <dir>/cell.toml (package root), not <dir>/.cell/cell.toml
|
||||
Build.load_config = function(dir) {
|
||||
var path = dir + '/.cell/cell.toml'
|
||||
var path = dir + '/cell.toml'
|
||||
if (!fd.is_file(path)) return {}
|
||||
var content = fd.slurp(path)
|
||||
if (!content || !content.length) return {}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
// cell clean - Remove build artifacts from modules/
|
||||
// cell clean - Remove build artifacts from global shop
|
||||
|
||||
var fd = use('fd')
|
||||
var shop = use('shop')
|
||||
|
||||
if (!fd.is_dir('.cell/build')) {
|
||||
log.console("No build directory found")
|
||||
var build_dir = shop.get_shop_path() + '/build'
|
||||
|
||||
if (!fd.is_dir(build_dir)) {
|
||||
log.console("No build directory found at " + build_dir)
|
||||
$_.stop()
|
||||
return
|
||||
}
|
||||
@@ -12,8 +15,8 @@ log.console("Cleaning build artifacts...")
|
||||
|
||||
// Remove the build directory
|
||||
try {
|
||||
fd.rm('.cell/build')
|
||||
log.console("Build directory removed")
|
||||
fd.rm(build_dir)
|
||||
log.console("Build directory removed: " + build_dir)
|
||||
} catch (e) {
|
||||
log.error(e)
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ if (args.length == 0) {
|
||||
|
||||
var config = shop.load_config()
|
||||
if (!config) {
|
||||
log.error("Failed to load .cell/cell.toml")
|
||||
log.error("Failed to load cell.toml")
|
||||
$_.stop()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -37,22 +37,36 @@ globalThis.clone = function(obj) {
|
||||
}
|
||||
}
|
||||
|
||||
var qop = use_embed('qop')
|
||||
var core_qop = qop.open(hidden.core_qop_blob)
|
||||
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')
|
||||
}
|
||||
|
||||
var use_cache = {}
|
||||
|
||||
// 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 sym = use_embed(path)
|
||||
var script = core_qop.read(path + MOD_EXT);
|
||||
if (script) {
|
||||
script = utf8.decode(script)
|
||||
// Core scripts are now in .cell/core/scripts
|
||||
var file_path = core_path + '/scripts/' + path + MOD_EXT
|
||||
|
||||
// Fallback check for root (optional, for backward compat if needed, but not strictly necessary if we are consistent)
|
||||
if (!fd.is_file(file_path)) {
|
||||
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_${path}_module($_){${script}})`
|
||||
var fn = js.eval(path, mod)
|
||||
var result = fn.call(sym);
|
||||
@@ -194,7 +208,6 @@ function create_actor(desc = {id:guid()}) {
|
||||
var $_ = create_actor()
|
||||
|
||||
var shop = use('shop')
|
||||
os.core_qop = core_qop
|
||||
os.use_cache = use_cache
|
||||
shop.set_os(os, $_)
|
||||
|
||||
@@ -205,7 +218,7 @@ 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 = '.cell/modules/'
|
||||
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('/')
|
||||
@@ -630,30 +643,26 @@ function report_to_overling(msg)
|
||||
sys_msg(overling, {kind:'underling', message:msg, from: $_})
|
||||
}
|
||||
|
||||
// Check for embedded entry point configuration
|
||||
var entry_config = null
|
||||
var entry_data = core_qop.read('__entry__.json')
|
||||
if (entry_data) {
|
||||
try {
|
||||
entry_config = json.decode(text(utf8.decode(entry_data)))
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
// Determine the program to run
|
||||
// Priority: 1. command line arg, 2. embedded entry point, 3. error
|
||||
// Determine the program to run from command line
|
||||
var program = cell.args.program
|
||||
if (!program && entry_config && entry_config.entry) {
|
||||
program = entry_config.entry
|
||||
cell.args.program = program
|
||||
}
|
||||
|
||||
// Store static_only mode for shop.cm to use
|
||||
if (entry_config && entry_config.static_only) {
|
||||
cell.static_only = true
|
||||
}
|
||||
|
||||
if (!program)
|
||||
if (!program) {
|
||||
log.error('No program specified. Usage: cell <program.ce> [args...]')
|
||||
os.exit(1)
|
||||
}
|
||||
|
||||
// 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]
|
||||
|
||||
@@ -665,14 +665,14 @@ JSC_CCALL(fd_realpath,
|
||||
DWORD len = GetFullPathNameA(path, PATH_MAX, resolved, NULL);
|
||||
JS_FreeCString(js, path);
|
||||
if (len == 0 || len >= PATH_MAX) {
|
||||
return JS_ThrowInternalError(js, "realpath failed: %s", strerror(errno));
|
||||
return JS_ThrowInternalError(js, "realpath failed for %s: %s", path, strerror(errno));
|
||||
}
|
||||
return JS_NewString(js, resolved);
|
||||
#else
|
||||
char *resolved = realpath(path, NULL);
|
||||
JS_FreeCString(js, path);
|
||||
if (!resolved) {
|
||||
return JS_ThrowInternalError(js, "realpath failed: %s", strerror(errno));
|
||||
return JS_ThrowInternalError(js, "realpath failed for %s: %s", path, strerror(errno));
|
||||
}
|
||||
JSValue result = JS_NewString(js, resolved);
|
||||
free(resolved);
|
||||
|
||||
30
scripts/install.ce
Normal file
30
scripts/install.ce
Normal file
@@ -0,0 +1,30 @@
|
||||
// cell install <locator> - Install a package to the shop
|
||||
// Does not modify the current project's cell.toml
|
||||
|
||||
var shop = use('shop')
|
||||
|
||||
if (args.length < 1) {
|
||||
log.console("Usage: cell install <locator>")
|
||||
$_.stop()
|
||||
return
|
||||
}
|
||||
|
||||
var locator = args[0]
|
||||
|
||||
log.console("Installing " + locator + "...")
|
||||
if (!shop.update(locator)) {
|
||||
log.console("Failed to install " + locator)
|
||||
$_.exit(1)
|
||||
}
|
||||
|
||||
var deps = shop.list_packages(locator)
|
||||
for (var dep of deps) {
|
||||
log.console("Installing dependency " + dep)
|
||||
shop.update(dep)
|
||||
shop.build_package(dep)
|
||||
}
|
||||
|
||||
shop.build_package(locator)
|
||||
log.console("Installed " + locator)
|
||||
|
||||
$_.stop()
|
||||
@@ -11,6 +11,8 @@ var target_pkg = null
|
||||
if (args && args.length > 0) {
|
||||
if (args[0] == 'all') {
|
||||
mode = 'all'
|
||||
} else if (args[0] == 'shop') {
|
||||
mode = 'shop'
|
||||
} else if (args[0] == 'package') {
|
||||
if (args.length < 2) {
|
||||
log.console("Usage: cell list package <name>")
|
||||
@@ -24,6 +26,7 @@ if (args && args.length > 0) {
|
||||
log.console(" cell list : list local packages")
|
||||
log.console(" cell list all : list all recursive packages")
|
||||
log.console(" cell list package <name>: list dependencies of <name>")
|
||||
log.console(" cell list shop : list all packages in shop")
|
||||
$_.stop()
|
||||
return
|
||||
}
|
||||
@@ -51,6 +54,21 @@ if (mode == 'local') {
|
||||
log.console(" " + all[i])
|
||||
}
|
||||
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
|
||||
|
||||
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 + "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function print_deps(ctx) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#define QOP_IMPLEMENTATION
|
||||
#include "qop.h"
|
||||
#include "cell.h"
|
||||
|
||||
@@ -77,16 +78,6 @@ static void write_64(unsigned long long v, FILE *fh) {
|
||||
fwrite(b, 8, 1, fh);
|
||||
}
|
||||
|
||||
static unsigned long long qop_hash(const char *key) {
|
||||
unsigned long long h = 525201411107845655ull;
|
||||
for (;*key;++key) {
|
||||
h ^= (unsigned char)*key;
|
||||
h *= 0x5bd1e9955bd1e995ull;
|
||||
h ^= h >> 47;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
static qop_desc *js2qop(JSContext *js, JSValue v) {
|
||||
return JS_GetOpaque(v, js_qop_archive_class_id);
|
||||
}
|
||||
|
||||
573
scripts/shop.cm
573
scripts/shop.cm
@@ -9,8 +9,6 @@ var crypto = use('crypto')
|
||||
var utf8 = use('utf8')
|
||||
var blob = use('blob')
|
||||
var build_utils = use('build')
|
||||
var qop
|
||||
var core_qop
|
||||
|
||||
// a package string is what is used to import a module, like prosperon/sprite
|
||||
// in prosperon/sprite, sprite is the module, and prosperon is the package (usually, an alias)
|
||||
@@ -18,6 +16,13 @@ var core_qop
|
||||
|
||||
var Shop = {}
|
||||
|
||||
// Global shop path - this is where all packages, builds, and cache are stored
|
||||
// Located at ~/.cell on the user's machine
|
||||
var global_shop_path = null
|
||||
|
||||
// Current package context - the package we're running from (resolved from program path)
|
||||
var current_package_path = null
|
||||
|
||||
var SCOPE_LOCAL = 0
|
||||
var SCOPE_PACKAGE = 1
|
||||
var SCOPE_CORE = 2
|
||||
@@ -31,22 +36,207 @@ var os
|
||||
var use_cache
|
||||
var platform
|
||||
var $_
|
||||
|
||||
// Initialize the global shop path
|
||||
function init_global_shop() {
|
||||
if (global_shop_path) return global_shop_path
|
||||
|
||||
var home = os.getenv('HOME')
|
||||
if (!home) {
|
||||
// Try Windows-style
|
||||
home = os.getenv('USERPROFILE')
|
||||
}
|
||||
if (!home) {
|
||||
throw new Error('Could not determine home directory. Set HOME environment variable.')
|
||||
}
|
||||
|
||||
global_shop_path = home + '/.cell'
|
||||
|
||||
// Verify the shop exists
|
||||
if (!fd.is_dir(global_shop_path)) {
|
||||
throw new Error('Cell shop not found at ' + global_shop_path + '. Run "cell install" to set up.')
|
||||
}
|
||||
|
||||
return global_shop_path
|
||||
}
|
||||
|
||||
// Get the global shop path
|
||||
Shop.get_shop_path = function() {
|
||||
return init_global_shop()
|
||||
}
|
||||
|
||||
// Find the package directory containing a file by looking for cell.toml
|
||||
// Returns the absolute path to the package directory, or null if not in a package
|
||||
Shop.find_package_dir = function(file_path) {
|
||||
if (!file_path) return null
|
||||
|
||||
// Walk up directories looking for cell.toml
|
||||
var dir = file_path
|
||||
if (fd.is_file(dir)) {
|
||||
// if it's a file, start from parent
|
||||
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 fd.realpath(dir)
|
||||
}
|
||||
var last_slash = dir.lastIndexOf('/')
|
||||
if (last_slash <= 0) break
|
||||
dir = dir.substring(0, last_slash)
|
||||
}
|
||||
|
||||
// Check current directory as fallback
|
||||
if (fd.is_file('cell.toml')) {
|
||||
return fd.realpath('.')
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// Link a local package into the shop
|
||||
function ensure_package_link(abs_path) {
|
||||
if (!abs_path || !abs_path.startsWith('/')) return false
|
||||
|
||||
var packages_dir = get_modules_dir()
|
||||
var target_link = packages_dir + abs_path
|
||||
|
||||
// If link already exists and points to correct place, we are good
|
||||
if (fd.is_link(target_link)) {
|
||||
var points_to = fd.readlink(target_link)
|
||||
if (points_to == abs_path) {
|
||||
update_local_lock(abs_path)
|
||||
return true
|
||||
}
|
||||
// Incorrect link, remove it
|
||||
fd.unlink(target_link)
|
||||
} else if (fd.is_dir(target_link)) {
|
||||
// If it's a real directory, that's weird for a local package mirror, but let's assume it might be a copy?
|
||||
// User wants symlinks.
|
||||
// If it's not a link, maybe we should leave it or warn?
|
||||
// Use user instructions: "shop should ... ensure it's there ... symlink local dirs"
|
||||
// safely assuming we can replace if it's not the right thing might be dangerous if user put real files there.
|
||||
// For now, if it's a dir, check if it's the package itself?
|
||||
// simpler: proceed to link logic
|
||||
}
|
||||
|
||||
// Create parent dirs
|
||||
var parent = target_link.substring(0, target_link.lastIndexOf('/'))
|
||||
ensure_dir(parent)
|
||||
|
||||
try {
|
||||
fd.symlink(abs_path, target_link)
|
||||
// log.console("Linked " + abs_path + " -> " + target_link)
|
||||
update_local_lock(abs_path)
|
||||
return true
|
||||
} catch (e) {
|
||||
log.error("Failed to link package " + abs_path + ": " + e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function update_local_lock(abs_path) {
|
||||
var lock = Shop.load_lock()
|
||||
var name = abs_path.split('/').pop()
|
||||
|
||||
// Check if we can find a better name from cell.toml
|
||||
var toml_path = abs_path + '/cell.toml'
|
||||
if (fd.is_file(toml_path)) {
|
||||
// We could parse it, but for now directory name is usually the package alias
|
||||
}
|
||||
|
||||
lock[name] = {
|
||||
package: abs_path,
|
||||
type: 'local',
|
||||
updated: time.number()
|
||||
}
|
||||
Shop.save_lock(lock)
|
||||
}
|
||||
|
||||
Shop.ensure_package_link = ensure_package_link
|
||||
|
||||
// Set the current package context from a program path
|
||||
Shop.set_current_package = function(program_path) {
|
||||
current_package_path = Shop.find_package_dir(program_path)
|
||||
|
||||
if (current_package_path && current_package_path.startsWith('/')) {
|
||||
// It's a local package, ensure it is linked in the shop
|
||||
ensure_package_link(current_package_path)
|
||||
}
|
||||
|
||||
return current_package_path
|
||||
}
|
||||
|
||||
// Get the current package path
|
||||
Shop.get_current_package = function() {
|
||||
return current_package_path
|
||||
}
|
||||
|
||||
Shop.set_os = function(o, $guy)
|
||||
{
|
||||
os = o
|
||||
$_ = $guy
|
||||
qop = os.load_internal('js_qop_use')
|
||||
core_qop = os.core_qop
|
||||
use_cache = os.use_cache
|
||||
|
||||
platform = os.platform()
|
||||
if (platform == 'macOS') dylib_ext = '.dylib'
|
||||
else if (platform == 'Windows') dylib_ext = '.dll'
|
||||
|
||||
// Initialize the global shop
|
||||
init_global_shop()
|
||||
}
|
||||
|
||||
var config = null
|
||||
|
||||
var shop_path = '.cell/cell.toml'
|
||||
// Get the config path for a package
|
||||
// If package_path is null, uses current_package_path
|
||||
function get_config_path(package_path) {
|
||||
if (package_path) {
|
||||
return package_path + '/cell.toml'
|
||||
}
|
||||
if (current_package_path) {
|
||||
return current_package_path + '/cell.toml'
|
||||
}
|
||||
// Fallback to current directory
|
||||
return 'cell.toml'
|
||||
}
|
||||
|
||||
// Get the lock file path (in the global shop)
|
||||
function get_lock_path() {
|
||||
return global_shop_path + '/lock.toml'
|
||||
}
|
||||
|
||||
// Get the packages directory (in the global shop)
|
||||
function get_modules_dir() {
|
||||
return global_shop_path + '/packages'
|
||||
}
|
||||
|
||||
// Get the cache directory (in the global shop)
|
||||
function get_cache_dir() {
|
||||
return global_shop_path + '/cache'
|
||||
}
|
||||
|
||||
// Get the build directory (in the global shop)
|
||||
function get_global_build_dir() {
|
||||
return global_shop_path + '/build'
|
||||
}
|
||||
|
||||
// Get the core directory (in the global shop)
|
||||
Shop.get_core_dir = function() {
|
||||
return global_shop_path + '/core'
|
||||
}
|
||||
|
||||
// Get the links file path (in the global shop)
|
||||
function get_links_path() {
|
||||
return global_shop_path + '/link.toml'
|
||||
}
|
||||
|
||||
// Get the reports directory (in the global shop)
|
||||
Shop.get_reports_dir = function() {
|
||||
return global_shop_path + '/reports'
|
||||
}
|
||||
|
||||
var open_dl = {}
|
||||
|
||||
@@ -81,57 +271,55 @@ Shop.file_info = function(file) {
|
||||
name_without_ext = file.substring(0, file.length - ACTOR_EXT.length)
|
||||
}
|
||||
|
||||
// Check if file is in a package
|
||||
if (file.startsWith('.cell/modules/')) {
|
||||
var rest = file.substring('.cell/modules/'.length)
|
||||
|
||||
// Get all packages and find which one matches this path
|
||||
var packages = Shop.list_packages()
|
||||
var matched_pkg = null
|
||||
var matched_path = null
|
||||
|
||||
for (var i = 0; i < packages.length; i++) {
|
||||
var pkg = packages[i]
|
||||
var parsed = Shop.parse_package(pkg)
|
||||
var pkg_path = parsed.path
|
||||
// Check if file is in a package (in global shop packages dir)
|
||||
var packages_prefix = get_modules_dir() + '/'
|
||||
if (file.startsWith(packages_prefix)) {
|
||||
var rest = file.substring(packages_prefix.length)
|
||||
|
||||
if (rest.startsWith(pkg_path + '/')) {
|
||||
// Found matching package - use longest match
|
||||
if (!matched_pkg || pkg_path.length > matched_path.length) {
|
||||
matched_pkg = pkg
|
||||
matched_path = pkg_path
|
||||
}
|
||||
// Get all packages and find which one matches this path
|
||||
// With the new structure, everything in packages/ is a package
|
||||
// gitea.pockle.world/user/repo/file.cm
|
||||
|
||||
var parts = rest.split('/')
|
||||
// Heuristic: packages form is host/user/repo
|
||||
if (parts.length >= 3) {
|
||||
var pkg_path = parts.slice(0, 3).join('/')
|
||||
info.package = pkg_path
|
||||
if (rest.length > pkg_path.length) {
|
||||
info.name = rest.substring(pkg_path.length + 1)
|
||||
} else {
|
||||
info.name = "" // root?
|
||||
}
|
||||
} else {
|
||||
// fallback
|
||||
info.package = rest
|
||||
}
|
||||
}
|
||||
|
||||
if (matched_path) {
|
||||
info.package = matched_path
|
||||
var import_part = rest.substring(matched_path.length + 1)
|
||||
// Strip extension from import name
|
||||
if (info.is_module && import_part.endsWith(MOD_EXT)) {
|
||||
import_part = import_part.substring(0, import_part.length - MOD_EXT.length)
|
||||
} else if (info.is_actor && import_part.endsWith(ACTOR_EXT)) {
|
||||
import_part = import_part.substring(0, import_part.length - ACTOR_EXT.length)
|
||||
|
||||
if (info.name) {
|
||||
if (info.is_module && info.name.endsWith(MOD_EXT))
|
||||
info.name = info.name.substring(0, info.name.length - MOD_EXT.length)
|
||||
if (info.is_actor && info.name.endsWith(ACTOR_EXT))
|
||||
info.name = info.name.substring(0, info.name.length - ACTOR_EXT.length)
|
||||
}
|
||||
info.name = import_part
|
||||
|
||||
} else {
|
||||
// Fallback: use first path component as package
|
||||
var slash_idx = rest.indexOf('/')
|
||||
if (slash_idx > 0) {
|
||||
info.package = rest.substring(0, slash_idx)
|
||||
var import_part = rest.substring(slash_idx + 1)
|
||||
if (info.is_module && import_part.endsWith(MOD_EXT)) {
|
||||
import_part = import_part.substring(0, import_part.length - MOD_EXT.length)
|
||||
} else if (info.is_actor && import_part.endsWith(ACTOR_EXT)) {
|
||||
import_part = import_part.substring(0, import_part.length - ACTOR_EXT.length)
|
||||
// Check if it's in any other package via cell.toml lookup
|
||||
var pkg_dir = Shop.find_package_dir(file)
|
||||
if (pkg_dir) {
|
||||
info.package = pkg_dir
|
||||
if (file.startsWith(pkg_dir + '/')) {
|
||||
var rel = file.substring(pkg_dir.length + 1)
|
||||
info.name = rel
|
||||
if (info.is_module && info.name.endsWith(MOD_EXT))
|
||||
info.name = info.name.substring(0, info.name.length - MOD_EXT.length)
|
||||
if (info.is_actor && info.name.endsWith(ACTOR_EXT))
|
||||
info.name = info.name.substring(0, info.name.length - ACTOR_EXT.length)
|
||||
}
|
||||
} else {
|
||||
info.package = 'local' // Should we keep 'local' for truly orphan files?
|
||||
info.name = name_without_ext
|
||||
}
|
||||
info.name = import_part
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info.package = 'local'
|
||||
info.name = name_without_ext
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
@@ -185,7 +373,7 @@ function get_import_dl(name) {
|
||||
var pkg = get_import_package(name)
|
||||
if (!pkg) return null
|
||||
if (open_dl[pkg]) return open_dl[pkg]
|
||||
var dlpath = `.cell/modules/${pkg}/${pkg}${dylib_ext}`
|
||||
var dlpath = get_modules_dir() + '/' + pkg + '/' + pkg + dylib_ext
|
||||
var dl = os.dylib_open(dlpath)
|
||||
if (dl) {
|
||||
open_dl[pkg] = dl
|
||||
@@ -209,13 +397,16 @@ var ensure_dir = build_utils.ensure_dir
|
||||
|
||||
Shop.load_config = function(module) {
|
||||
var content
|
||||
var config_path
|
||||
if (!module) {
|
||||
if (!fd.is_file(shop_path))
|
||||
config_path = get_config_path()
|
||||
if (!fd.is_file(config_path))
|
||||
return null
|
||||
content = fd.slurp(shop_path)
|
||||
content = fd.slurp(config_path)
|
||||
} else {
|
||||
var module_path = `.cell/modules/${module}/.cell/cell.toml`
|
||||
if (!fd.stat(module_path).isFile)
|
||||
// Module config is at <modules_dir>/<module>/cell.toml
|
||||
var module_path = get_modules_dir() + '/' + module + '/cell.toml'
|
||||
if (!fd.is_file(module_path))
|
||||
return null
|
||||
|
||||
content = fd.slurp(module_path)
|
||||
@@ -256,12 +447,13 @@ Shop.load_config = function(module) {
|
||||
|
||||
// Save cell.toml configuration
|
||||
Shop.save_config = function(config) {
|
||||
fd.slurpwrite(shop_path, utf8.encode(toml.encode(config)));
|
||||
var config_path = get_config_path()
|
||||
fd.slurpwrite(config_path, utf8.encode(toml.encode(config)));
|
||||
}
|
||||
|
||||
// Load lock.toml configuration
|
||||
// Load lock.toml configuration (from global shop)
|
||||
Shop.load_lock = function() {
|
||||
var path = '.cell/lock.toml'
|
||||
var path = get_lock_path()
|
||||
|
||||
if (!fd.is_file(path))
|
||||
return {}
|
||||
@@ -298,15 +490,17 @@ Shop.load_lock = function() {
|
||||
return lock
|
||||
}
|
||||
|
||||
// Save lock.toml configuration
|
||||
// Save lock.toml configuration (to global shop)
|
||||
Shop.save_lock = function(lock) {
|
||||
fd.slurpwrite('.cell/lock.toml', utf8.encode(toml.encode(lock)));
|
||||
var path = get_lock_path()
|
||||
ensure_dir(global_shop_path)
|
||||
fd.slurpwrite(path, utf8.encode(toml.encode(lock)));
|
||||
}
|
||||
|
||||
var link_cache = null
|
||||
Shop.load_links = function() {
|
||||
if (link_cache) return link_cache
|
||||
var path = '.cell/link.toml'
|
||||
var path = get_links_path()
|
||||
if (!fd.is_file(path)) {
|
||||
link_cache = {}
|
||||
return link_cache
|
||||
@@ -326,7 +520,9 @@ Shop.load_links = function() {
|
||||
Shop.save_links = function(links) {
|
||||
link_cache = links
|
||||
var cfg = { links: links }
|
||||
fd.slurpwrite('.cell/link.toml', utf8.encode(toml.encode(cfg)))
|
||||
var path = get_links_path()
|
||||
ensure_dir(global_shop_path)
|
||||
fd.slurpwrite(path, utf8.encode(toml.encode(cfg)))
|
||||
}
|
||||
|
||||
Shop.add_link = function(canonical, target) {
|
||||
@@ -396,19 +592,20 @@ Shop.resolve_package_info = function(pkg) {
|
||||
|
||||
var path = pkg
|
||||
if (path.includes('@')) path = path.split('@')[0]
|
||||
if (path.startsWith('/')) path = path.substring(1)
|
||||
|
||||
var links = Shop.load_links()
|
||||
if (links[path]) {
|
||||
return { type: 'local', path: links[path] }
|
||||
return { type: 'package', path: links[path] }
|
||||
}
|
||||
|
||||
if (pkg.startsWith('/')) {
|
||||
return { type: 'local', path: pkg }
|
||||
return { type: 'package', path: pkg }
|
||||
}
|
||||
if (pkg.includes('gitea.')) {
|
||||
return { type: 'gitea' }
|
||||
}
|
||||
|
||||
// If it looks like a path but isn't absolute, might be relative in future, but for now strict
|
||||
return { type: 'unknown' }
|
||||
}
|
||||
|
||||
@@ -513,7 +710,7 @@ Shop.get_module_dir = function(alias) {
|
||||
var parsed = Shop.parse_package(pkg)
|
||||
if (!parsed) return null
|
||||
|
||||
return '.cell/modules/' + parsed.path
|
||||
return get_modules_dir() + '/' + parsed.path
|
||||
}
|
||||
|
||||
function lock_package(loc)
|
||||
@@ -526,7 +723,7 @@ Shop.check_cache = function(pkg) {
|
||||
var parsed = Shop.parse_package(pkg)
|
||||
if (!parsed) return null
|
||||
|
||||
var cache_path = `.cell/cache/${parsed.path}.zip`
|
||||
var cache_path = get_cache_dir() + '/' + parsed.path + '.zip'
|
||||
if (fd.is_file(cache_path)) {
|
||||
log.console("Found cached zip: " + cache_path)
|
||||
return true
|
||||
@@ -545,11 +742,12 @@ var script_forms = []
|
||||
// core:<module path>
|
||||
// <package>:<module path> (package is the full canonical package name)
|
||||
// local:<module path>
|
||||
// package:<module path> (fallback when pkg isn't provided but path is under .cell/modules)
|
||||
// package:<module path> (fallback when pkg isn't provided but path is under modules)
|
||||
function make_compile_name(path, rel_path, pkg, scope) {
|
||||
if (scope == SCOPE_CORE) return 'core:' + rel_path
|
||||
if (pkg) return pkg + ':' + rel_path
|
||||
if (path && path.startsWith('.cell/modules/')) return 'package:' + rel_path
|
||||
var modules_dir = get_modules_dir()
|
||||
if (path && path.startsWith(modules_dir + '/')) return 'package:' + rel_path
|
||||
return 'local:' + rel_path
|
||||
}
|
||||
|
||||
@@ -581,15 +779,39 @@ function get_flags(config, platform, key) {
|
||||
|
||||
Shop.get_flags = get_flags
|
||||
|
||||
function get_build_dir(pkg = 'local') {
|
||||
return '.cell/build/' + pkg
|
||||
function get_build_dir(pkg) {
|
||||
if (!pkg) pkg = current_package_path
|
||||
if (!pkg) return get_global_build_dir() + '/local' // Fallback for non-package scripts
|
||||
|
||||
// If pkg is absolute path, mirror it
|
||||
if (pkg.startsWith('/')) {
|
||||
// Accio folder should be .cell/packages/Users/...
|
||||
// But build should be .cell/build/packages/Users/...
|
||||
return get_global_build_dir() + '/packages' + pkg
|
||||
}
|
||||
|
||||
// Otherwise it's a relative package path (from packages dir)
|
||||
return get_global_build_dir() + '/packages/' + pkg
|
||||
}
|
||||
|
||||
Shop.get_build_dir = get_build_dir
|
||||
|
||||
function get_rel_path(path, pkg) {
|
||||
if (!pkg) return path
|
||||
var prefix = '.cell/modules/' + pkg + '/'
|
||||
if (!pkg) {
|
||||
// For local files, strip the current package path prefix if present
|
||||
if (current_package_path && path.startsWith(current_package_path + '/')) {
|
||||
return path.substring(current_package_path.length + 1)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
var prefix
|
||||
if (pkg.startsWith('/')) {
|
||||
prefix = pkg + '/'
|
||||
} else {
|
||||
prefix = get_modules_dir() + '/' + pkg + '/'
|
||||
}
|
||||
|
||||
if (path.startsWith(prefix)) {
|
||||
return path.substring(prefix.length)
|
||||
}
|
||||
@@ -600,7 +822,19 @@ function resolve_mod_fn(path, pkg)
|
||||
{
|
||||
if (!fd.is_file(path)) throw new Error(`path ${path} is not a file`)
|
||||
var rel_path = get_rel_path(path, pkg)
|
||||
var build_dir = get_build_dir(pkg)
|
||||
|
||||
// Determine build directory based on context
|
||||
var build_dir
|
||||
if (pkg) {
|
||||
build_dir = get_build_dir(pkg)
|
||||
} else if (current_package_path) {
|
||||
// Local file in current package - use package path (strip leading /)
|
||||
var pkg_id = current_package_path.substring(1) // Remove leading /
|
||||
build_dir = get_global_build_dir() + '/' + pkg_id
|
||||
} else {
|
||||
build_dir = get_build_dir('local')
|
||||
}
|
||||
|
||||
var cache_path = build_dir + '/' + rel_path + '.o'
|
||||
|
||||
if (fd.is_file(cache_path) && fd.stat(path).mtime <= fd.stat(cache_path).mtime) {
|
||||
@@ -622,15 +856,48 @@ function resolve_mod_fn(path, pkg)
|
||||
return js.eval_compile(fn)
|
||||
}
|
||||
|
||||
// Resolve and cache a core module
|
||||
function resolve_core_mod_fn(core_path, rel_path) {
|
||||
var build_dir = get_global_build_dir() + '/core'
|
||||
var cache_path = build_dir + '/' + rel_path + '.o'
|
||||
|
||||
if (fd.is_file(cache_path) && fd.stat(core_path).mtime <= fd.stat(cache_path).mtime) {
|
||||
var obj = fd.slurp(cache_path)
|
||||
var fn = js.compile_unblob(obj)
|
||||
return js.eval_compile(fn)
|
||||
}
|
||||
|
||||
var ext = core_path.substring(core_path.lastIndexOf('.'))
|
||||
var form = script_forms[ext]
|
||||
if (!form) throw new Error(`No script form for extension ${ext}`)
|
||||
|
||||
var compile_name = make_compile_name(core_path, rel_path, null, SCOPE_CORE)
|
||||
var script = form(null, text(fd.slurp(core_path)))
|
||||
var fn = js.compile(compile_name, script)
|
||||
ensure_dir(cache_path.substring(0, cache_path.lastIndexOf('/')))
|
||||
fd.slurpwrite(cache_path, js.compile_blob(fn))
|
||||
return js.eval_compile(fn)
|
||||
}
|
||||
|
||||
function resolve_locator(path, ext, ctx)
|
||||
{
|
||||
// In static_only mode, only look in the embedded pack
|
||||
var static_only = cell.static_only
|
||||
|
||||
if (!static_only) {
|
||||
// First, check if file exists in current package directory
|
||||
if (current_package_path) {
|
||||
var pkg_local_path = current_package_path + '/' + path + ext
|
||||
if (fd.is_file(pkg_local_path)) {
|
||||
var fn = resolve_mod_fn(pkg_local_path, null)
|
||||
return {path: pkg_local_path, scope: SCOPE_LOCAL, symbol:fn}
|
||||
}
|
||||
}
|
||||
|
||||
// Check CWD for local file
|
||||
var local_path
|
||||
if (ctx)
|
||||
local_path = `.cell/modules/${ctx}/${path}${ext}`
|
||||
local_path = get_modules_dir() + '/' + ctx + '/' + path + ext
|
||||
else
|
||||
local_path = path + ext
|
||||
|
||||
@@ -639,38 +906,37 @@ function resolve_locator(path, ext, ctx)
|
||||
return {path: local_path, scope: SCOPE_LOCAL, symbol:fn}
|
||||
}
|
||||
|
||||
// Check installed packages
|
||||
var canonical_pkg = get_normalized_package(path, ctx)
|
||||
var pkg_path = get_path_in_package(path, ctx)
|
||||
var mod_path = `.cell/modules/${pkg_path}${ext}`
|
||||
var mod_path = get_modules_dir() + '/' + pkg_path + ext
|
||||
if (fd.is_file(mod_path)) {
|
||||
var fn = resolve_mod_fn(mod_path, canonical_pkg)
|
||||
return {path: mod_path, scope: SCOPE_PACKAGE, symbol:fn}
|
||||
}
|
||||
}
|
||||
|
||||
// Check embedded pack (core_qop)
|
||||
// For packages, try the full package path first
|
||||
// Check core directory for core modules
|
||||
var core_dir = Shop.get_core_dir()
|
||||
|
||||
// For packages, try the full package path first in core
|
||||
if (ctx) {
|
||||
var pkg_rel = ctx + '/' + path + ext
|
||||
var pkg_core = core_qop.read(pkg_rel)
|
||||
if (pkg_core != null) {
|
||||
var form = script_forms[ext]
|
||||
if (!form) throw new Error(`No script form for extension ${ext}`)
|
||||
var script = form(null, text(pkg_core), ctx)
|
||||
var compile_name = make_compile_name(pkg_rel, pkg_rel, ctx, SCOPE_CORE)
|
||||
var fn = js.compile(compile_name, script)
|
||||
return {path: pkg_rel, scope: SCOPE_CORE, symbol:js.eval_compile(fn)};
|
||||
var pkg_core_path = core_dir + '/' + pkg_rel
|
||||
if (fd.is_file(pkg_core_path)) {
|
||||
var fn = resolve_core_mod_fn(pkg_core_path, pkg_rel)
|
||||
return {path: pkg_rel, scope: SCOPE_CORE, symbol:fn};
|
||||
}
|
||||
}
|
||||
|
||||
var core = core_qop.read(path + ext)
|
||||
if (core != null) {
|
||||
var form = script_forms[ext]
|
||||
if (!form) throw new Error(`No script form for extension ${ext}`)
|
||||
var script = form(null,text(core))
|
||||
var compile_name = make_compile_name(path + ext, path + ext, null, SCOPE_CORE)
|
||||
var fn = js.compile(compile_name, script)
|
||||
return {path: path + ext, scope: SCOPE_CORE, symbol:js.eval_compile(fn)};
|
||||
// Check core directory for the module
|
||||
// Core scripts are now in .cell/core/scripts
|
||||
var core_dir = Shop.get_core_dir()
|
||||
var core_file_path = core_dir + '/scripts/' + path + ext
|
||||
if (path == 'text') log.console("Checking core mod: " + core_file_path + " exists: " + fd.is_file(core_file_path))
|
||||
if (fd.is_file(core_file_path)) {
|
||||
var fn = resolve_core_mod_fn(core_file_path, path + ext)
|
||||
return {path: path + ext, scope: SCOPE_CORE, symbol:fn};
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -891,7 +1157,7 @@ function get_cache_path(pkg, commit) {
|
||||
if (!parsed) return null
|
||||
|
||||
var slug = parsed.path.split('/').join('_')
|
||||
return `.cell/cache/${slug}_${commit}.zip`
|
||||
return get_cache_dir() + '/' + slug + '_' + commit + '.zip'
|
||||
}
|
||||
|
||||
function rm_recursive(path) {
|
||||
@@ -995,7 +1261,7 @@ Shop.update = function(pkg) {
|
||||
var lock = Shop.load_lock()
|
||||
var parsed = Shop.parse_package(pkg)
|
||||
var info = Shop.resolve_package_info(pkg)
|
||||
var target_dir = '.cell/modules/' + parsed.path
|
||||
var target_dir = get_modules_dir() + '/' + parsed.path
|
||||
|
||||
var result = info.type == 'local'
|
||||
? update_local(pkg, info, target_dir)
|
||||
@@ -1186,7 +1452,7 @@ Shop.remove = function(alias) {
|
||||
|
||||
var locator = config.dependencies[alias]
|
||||
var parsed = Shop.parse_package(locator)
|
||||
var target_dir = '.cell/modules/' + parsed.path
|
||||
var target_dir = get_modules_dir() + '/' + parsed.path
|
||||
|
||||
// Remove from config
|
||||
delete config.dependencies[alias]
|
||||
@@ -1239,8 +1505,8 @@ Shop.remove_replacement = function(alias) {
|
||||
// List all files in a package
|
||||
Shop.list_files = function(pkg) {
|
||||
var dir
|
||||
if (!pkg) dir = '.'
|
||||
else dir = '.cell/modules/' + pkg
|
||||
if (!pkg) dir = current_package_path || '.'
|
||||
else dir = get_modules_dir() + '/' + pkg
|
||||
|
||||
var files = []
|
||||
|
||||
@@ -1328,7 +1594,7 @@ Shop.build_package = function(package)
|
||||
var build_dir = get_build_dir(package)
|
||||
ensure_dir(build_dir)
|
||||
|
||||
var module_dir = package ? '.cell/modules/' + package : '.'
|
||||
var module_dir = package ? get_modules_dir() + '/' + package : (current_package_path || '.')
|
||||
|
||||
log.console(`Building package ${package ? package : 'local'} to ${build_dir}`)
|
||||
|
||||
@@ -1349,8 +1615,15 @@ Shop.build_package = function(package)
|
||||
for (var i=0; i<files.length; i++) {
|
||||
var file = files[i]
|
||||
var src_path
|
||||
if (!package) src_path = file
|
||||
else src_path = '.cell/modules/' + package + '/' + file
|
||||
if (!package) {
|
||||
src_path = (current_package_path || '.') + '/' + file
|
||||
} else if (package.startsWith('/')) {
|
||||
// Local absolute package
|
||||
src_path = package + '/' + file
|
||||
} else {
|
||||
// Regular package
|
||||
src_path = get_modules_dir() + '/' + package + '/' + file
|
||||
}
|
||||
|
||||
if (file.endsWith('.cm') || file.endsWith('.ce')) {
|
||||
// Compile module
|
||||
@@ -1477,10 +1750,10 @@ Shop.build_package = function(package)
|
||||
var temp_lib = 'cellmod' + dylib_ext
|
||||
var objs_str = ''
|
||||
for (var i=0; i<c_objects.length; i++) {
|
||||
objs_str += '"$HERE/' + c_objects[i] + '" '
|
||||
objs_str += '"' + c_objects[i] + '" '
|
||||
}
|
||||
|
||||
var link_cmd = 'HERE=$(pwd); 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 + ' -lcell_runtime -lc -lc++ -o ' + temp_lib
|
||||
var link_cmd_hash = get_hash(link_cmd)
|
||||
|
||||
// Check if we need to relink
|
||||
@@ -1596,7 +1869,7 @@ Shop.resolve_module = function(module_name, package_name, is_file_fn) {
|
||||
|
||||
// If we're in a package context, check the package first
|
||||
if (package_name) {
|
||||
var pkg_path = '.cell/modules/' + package_name + '/' + module_name + '.cm'
|
||||
var pkg_path = get_modules_dir() + '/' + package_name + '/' + module_name + '.cm'
|
||||
if (is_file_fn(pkg_path)) {
|
||||
return { path: pkg_path, package_name: package_name }
|
||||
}
|
||||
@@ -1615,20 +1888,22 @@ Shop.resolve_module = function(module_name, package_name, is_file_fn) {
|
||||
var parsed = Shop.parse_package(pkg)
|
||||
var canonical_path = parsed.path
|
||||
|
||||
var dep_path = '.cell/modules/' + canonical_path + '/' + sub_module + '.cm'
|
||||
var dep_path = get_modules_dir() + '/' + canonical_path + '/' + sub_module + '.cm'
|
||||
if (is_file_fn(dep_path)) {
|
||||
return { path: dep_path, package_name: pkg_alias }
|
||||
}
|
||||
}
|
||||
|
||||
// Check local path (relative to project root)
|
||||
var local_path = module_name + '.cm'
|
||||
// Check local path (relative to current package)
|
||||
var local_base = current_package_path || '.'
|
||||
var local_path = local_base + '/' + module_name + '.cm'
|
||||
if (is_file_fn(local_path)) {
|
||||
return { path: local_path, package_name: null }
|
||||
}
|
||||
} else {
|
||||
// Simple module name - check local first, then dependencies
|
||||
var local_path = module_name + '.cm'
|
||||
var local_base = current_package_path || '.'
|
||||
var local_path = local_base + '/' + module_name + '.cm'
|
||||
if (is_file_fn(local_path)) {
|
||||
return { path: local_path, package_name: null }
|
||||
}
|
||||
@@ -1639,7 +1914,7 @@ Shop.resolve_module = function(module_name, package_name, is_file_fn) {
|
||||
var parsed = Shop.parse_package(pkg)
|
||||
var canonical_path = parsed.path
|
||||
|
||||
var dep_path = '.cell/modules/' + canonical_path + '/' + module_name + '.cm'
|
||||
var dep_path = get_modules_dir() + '/' + canonical_path + '/' + module_name + '.cm'
|
||||
if (is_file_fn(dep_path)) {
|
||||
return { path: dep_path, package_name: alias }
|
||||
}
|
||||
@@ -1664,4 +1939,82 @@ Shop.resolve_alias = function(name, ctx) {
|
||||
}
|
||||
|
||||
Shop.get_canonical_package = get_canonical_package
|
||||
|
||||
Shop.upgrade_core = function() {
|
||||
var pkg = "gitea.pockle.world/john/cell"
|
||||
var hash = fetch_remote_hash(pkg)
|
||||
|
||||
if (!hash) {
|
||||
// Fallback to master?
|
||||
log.console("Could not fetch remote hash for core, assuming master.")
|
||||
hash = "master"
|
||||
}
|
||||
|
||||
var zip = get_or_download_zip(pkg, hash)
|
||||
if (!zip) return false
|
||||
|
||||
var core_dir = Shop.get_core_dir()
|
||||
install_zip(zip, core_dir)
|
||||
|
||||
var lock = Shop.load_lock()
|
||||
lock['core'] = {
|
||||
package: pkg,
|
||||
commit: hash,
|
||||
updated: time.number()
|
||||
}
|
||||
Shop.save_lock(lock)
|
||||
|
||||
log.console("Core updated to " + hash)
|
||||
return true
|
||||
}
|
||||
|
||||
Shop.link_core = function(target) {
|
||||
if (!fd.is_dir(target)) return false
|
||||
var core_dir = Shop.get_core_dir()
|
||||
|
||||
if (fd.is_link(core_dir)) fd.unlink(core_dir)
|
||||
else if (fd.is_dir(core_dir)) {
|
||||
// Safety check?
|
||||
// For now, we assume user knows what they are doing.
|
||||
// We can't easily rm recursive if it's a real dir unless we use system rm or implement it.
|
||||
// shop.cm has rm_recursive? No, I implemented it?
|
||||
// I saw `function rm_recursive` at line 1141. internal.
|
||||
rm_recursive(core_dir)
|
||||
}
|
||||
|
||||
fd.symlink(fd.realpath(target), core_dir)
|
||||
|
||||
var lock = Shop.load_lock()
|
||||
lock['core'] = {
|
||||
package: fd.realpath(target),
|
||||
type: 'local',
|
||||
updated: time.number()
|
||||
}
|
||||
Shop.save_lock(lock)
|
||||
return true
|
||||
}
|
||||
|
||||
Shop.unlink_core = function() {
|
||||
var core_dir = Shop.get_core_dir()
|
||||
if (fd.is_link(core_dir)) {
|
||||
fd.unlink(core_dir)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Shop.is_core_linked = function() {
|
||||
var core_dir = Shop.get_core_dir()
|
||||
return fd.is_link(core_dir)
|
||||
}
|
||||
|
||||
Shop.list_shop_packages = function() {
|
||||
var lock = Shop.load_lock()
|
||||
var list = []
|
||||
for (var k in lock) {
|
||||
if (lock[k]) list.push(lock[k])
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
return Shop
|
||||
@@ -58,8 +58,8 @@ function ensure_dir(path) {
|
||||
|
||||
// Collect .ce actor tests from a package
|
||||
function collect_actor_tests(pkg) {
|
||||
var prefix = pkg ? `.cell/modules/${pkg}` : "."
|
||||
var tests_dir = `${prefix}/tests`
|
||||
var prefix = pkg ? shop.get_shop_path() + '/modules/' + pkg : (shop.get_current_package() || '.')
|
||||
var tests_dir = prefix + '/tests'
|
||||
|
||||
if (!fd.is_dir(tests_dir)) return []
|
||||
|
||||
@@ -110,8 +110,8 @@ function spawn_actor_test(test_info) {
|
||||
}
|
||||
|
||||
function run_tests(pkg) {
|
||||
var prefix = pkg ? `.cell/modules/${pkg}` : "."
|
||||
var tests_dir = `${prefix}/tests`
|
||||
var prefix = pkg ? shop.get_shop_path() + '/modules/' + pkg : (shop.get_current_package() || '.')
|
||||
var tests_dir = prefix + '/tests'
|
||||
|
||||
var pkg_result = {
|
||||
package: pkg || "local",
|
||||
@@ -448,7 +448,7 @@ if (all_actor_tests.length == 0) {
|
||||
// Generate Reports function
|
||||
function generate_reports(totals) {
|
||||
var timestamp = Math.floor(time.number()).toString()
|
||||
var report_dir = `.cell/reports/test_${timestamp}`
|
||||
var report_dir = shop.get_reports_dir() + '/test_' + timestamp
|
||||
ensure_dir(report_dir)
|
||||
|
||||
// Generate test.txt
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Simple TOML parser for shop.toml
|
||||
// Simple TOML parser for cell modules
|
||||
// Supports basic TOML features needed for the module system
|
||||
|
||||
function parse_toml(text) {
|
||||
@@ -16,8 +16,9 @@ function parse_toml(text) {
|
||||
|
||||
// Section header
|
||||
if (line.startsWith('[') && line.endsWith(']')) {
|
||||
var section_path = line.slice(1, -1).split('.')
|
||||
var section_path = parse_key_path(line.slice(1, -1))
|
||||
current_section = result
|
||||
// Reconstruct name for debugging/legacy (not strictly needed for object construction)
|
||||
current_section_name = section_path.join('.')
|
||||
|
||||
for (var j = 0; j < section_path.length; j++) {
|
||||
@@ -33,9 +34,13 @@ function parse_toml(text) {
|
||||
// Key-value pair
|
||||
var eq_index = line.indexOf('=')
|
||||
if (eq_index > 0) {
|
||||
var key = line.substring(0, eq_index).trim()
|
||||
var key_part = line.substring(0, eq_index).trim()
|
||||
var value = line.substring(eq_index + 1).trim()
|
||||
|
||||
// Handle quoted keys in key-value pairs too if needed?
|
||||
// For now assuming simple keys or quoted keys
|
||||
var key = parse_key(key_part)
|
||||
|
||||
// Parse value
|
||||
if (value.startsWith('"') && value.endsWith('"')) {
|
||||
// String - unescape quotes
|
||||
@@ -59,6 +64,37 @@ function parse_toml(text) {
|
||||
return result
|
||||
}
|
||||
|
||||
function parse_key(str) {
|
||||
if (str.startsWith('"') && str.endsWith('"')) {
|
||||
return str.slice(1, -1).replace(/\\"/g, '"')
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// Split a key path by dots, respecting quotes
|
||||
function parse_key_path(str) {
|
||||
var parts = []
|
||||
var current = ''
|
||||
var in_quote = false
|
||||
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
var c = str[i]
|
||||
if (c == '"' && (i==0 || str[i-1] != '\\')) {
|
||||
in_quote = !in_quote
|
||||
// We don't verify if it's strictly correct TOML quote usage, just rudimentary
|
||||
} else if (c == '.' && !in_quote) {
|
||||
parts.push(parse_key(current.trim()))
|
||||
current = ''
|
||||
continue
|
||||
}
|
||||
current += c
|
||||
}
|
||||
if (current.trim().length > 0)
|
||||
parts.push(parse_key(current.trim()))
|
||||
|
||||
return parts
|
||||
}
|
||||
|
||||
function parse_array(str) {
|
||||
// Remove brackets
|
||||
str = str.slice(1, -1).trim()
|
||||
@@ -121,13 +157,20 @@ function encode_toml(obj) {
|
||||
return String(value)
|
||||
}
|
||||
|
||||
function quote_key(k) {
|
||||
if (k.includes('.') || k.includes('"') || k.includes(' ')) {
|
||||
return '"' + k.replace(/"/g, '\\"') + '"'
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
// First pass: encode top-level simple values
|
||||
var keys = Object.keys(obj)
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i]
|
||||
var value = obj[key]
|
||||
if (value == null || typeof value != 'object' || Array.isArray(value)) {
|
||||
result.push(key + ' = ' + encode_value(value))
|
||||
result.push(quote_key(key) + ' = ' + encode_value(value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +184,10 @@ function encode_toml(obj) {
|
||||
|
||||
if (value != null && typeof value == 'object' && !Array.isArray(value)) {
|
||||
// Nested object - create section
|
||||
var section_path = path ? path + '.' + key : key
|
||||
// We MUST quote the key segment if it has dots, otherwise it becomes a nested table path
|
||||
var quoted = quote_key(key)
|
||||
var section_path = path ? path + '.' + quoted : quoted
|
||||
|
||||
result.push('[' + section_path + ']')
|
||||
|
||||
// First encode direct properties of this section
|
||||
@@ -150,7 +196,7 @@ function encode_toml(obj) {
|
||||
var sk = section_keys[j]
|
||||
var sv = value[sk]
|
||||
if (sv == null || typeof sv != 'object' || Array.isArray(sv)) {
|
||||
result.push(sk + ' = ' + encode_value(sv))
|
||||
result.push(quote_key(sk) + ' = ' + encode_value(sv))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,14 @@ var shop = use('shop')
|
||||
|
||||
var alias = args.length > 0 ? args[0] : null
|
||||
|
||||
var packages = shop.list_packages()
|
||||
var packages = shop.list_shop_packages()
|
||||
|
||||
log.console("Checking for updates...")
|
||||
log.console("Checking for updates (" + packages.length + " packages)...")
|
||||
|
||||
for (var pack of 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)
|
||||
|
||||
37
scripts/upgrade.ce
Normal file
37
scripts/upgrade.ce
Normal file
@@ -0,0 +1,37 @@
|
||||
var shop = use('shop')
|
||||
var fd = use('fd')
|
||||
|
||||
var cmd = args.length > 0 ? args[0] : null
|
||||
|
||||
if (cmd == 'link') {
|
||||
if (args.length < 2) {
|
||||
log.console("Usage: cell upgrade link <core_dir>")
|
||||
return
|
||||
}
|
||||
var target = args[1]
|
||||
if (shop.link_core(target)) {
|
||||
log.console("Linked core -> " + fd.realpath(target))
|
||||
} else {
|
||||
log.console("Failed to link core to " + target)
|
||||
}
|
||||
} else if (cmd == 'unlink') {
|
||||
if (shop.unlink_core()) {
|
||||
log.console("Unlinked core.")
|
||||
} else {
|
||||
log.console("Core was not linked.")
|
||||
}
|
||||
log.console("Fetching latest core...")
|
||||
shop.upgrade_core()
|
||||
} else {
|
||||
// cell upgrade (no args)
|
||||
if (shop.is_core_linked()) {
|
||||
var core_dir = shop.get_core_dir()
|
||||
log.console("Core is linked to " + fd.readlink(core_dir))
|
||||
log.console("Unlink first to upgrade standard core.")
|
||||
} else {
|
||||
log.console("Fetching latest core...")
|
||||
shop.upgrade_core()
|
||||
}
|
||||
}
|
||||
|
||||
$_.stop()
|
||||
3
scripts/version.ce
Normal file
3
scripts/version.ce
Normal file
@@ -0,0 +1,3 @@
|
||||
var shop = use('shop')
|
||||
log.console("0.1.0")
|
||||
$_.stop()
|
||||
185
source/cell.c
185
source/cell.c
@@ -11,103 +11,100 @@
|
||||
#include "cell.h"
|
||||
#include "cell_internal.h"
|
||||
|
||||
#define QOP_IMPLEMENTATION
|
||||
#include "qop.h"
|
||||
|
||||
#define ENGINE "engine.cm"
|
||||
#define ENGINE "scripts/engine.cm"
|
||||
#define CELL_SHOP_DIR ".cell"
|
||||
#define CELL_CORE_DIR "core"
|
||||
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
static qop_desc qop_core;
|
||||
static qop_file *qop_hashmap = NULL;
|
||||
cell_rt *root_cell = NULL;
|
||||
static char *core_path = NULL;
|
||||
|
||||
int get_executable_path(char *buffer, unsigned int buffer_size) {
|
||||
#if defined(__linux__)
|
||||
ssize_t len = readlink("/proc/self/exe", buffer, buffer_size - 1);
|
||||
if (len == -1) {
|
||||
return 0;
|
||||
// Get the home directory
|
||||
static const char* get_home_dir(void) {
|
||||
const char *home = getenv("HOME");
|
||||
if (!home) {
|
||||
home = getenv("USERPROFILE"); // Windows fallback
|
||||
}
|
||||
buffer[len] = '\0';
|
||||
return len;
|
||||
#elif defined(__APPLE__)
|
||||
if (_NSGetExecutablePath(buffer, &buffer_size) == 0) {
|
||||
return buffer_size;
|
||||
}
|
||||
#elif defined(_WIN32)
|
||||
return GetModuleFileName(NULL, buffer, buffer_size);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
return home;
|
||||
}
|
||||
|
||||
int prosperon_mount_core(void)
|
||||
// Find and verify the cell shop at ~/.cell
|
||||
int find_cell_shop(void)
|
||||
{
|
||||
char exe_path[PATH_MAX];
|
||||
int exe_path_len = get_executable_path(exe_path, sizeof(exe_path));
|
||||
if (exe_path_len == 0) {
|
||||
printf("ERROR: Could not get executable path\n");
|
||||
const char *home = get_home_dir();
|
||||
if (!home) {
|
||||
printf("ERROR: Could not determine home directory. Set HOME environment variable.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Load the entire executable into memory
|
||||
FILE *fh = fopen(exe_path, "rb");
|
||||
if (!fh) {
|
||||
printf("ERROR: Could not open executable\n");
|
||||
// Build path to ~/.cell/core
|
||||
size_t path_len = strlen(home) + strlen("/" CELL_SHOP_DIR "/" CELL_CORE_DIR) + 1;
|
||||
core_path = malloc(path_len);
|
||||
if (!core_path) {
|
||||
printf("ERROR: Could not allocate memory for core path\n");
|
||||
return 0;
|
||||
}
|
||||
snprintf(core_path, path_len, "%s/" CELL_SHOP_DIR "/" CELL_CORE_DIR, home);
|
||||
|
||||
fseek(fh, 0, SEEK_END);
|
||||
long file_size = ftell(fh);
|
||||
fseek(fh, 0, SEEK_SET);
|
||||
|
||||
unsigned char *buf = malloc(file_size);
|
||||
if (!buf) {
|
||||
printf("ERROR: Could not allocate memory for executable\n");
|
||||
fclose(fh);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (fread(buf, 1, file_size, fh) != (size_t)file_size) {
|
||||
printf("ERROR: Could not read executable\n");
|
||||
free(buf);
|
||||
fclose(fh);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fclose(fh);
|
||||
|
||||
// Open the QOP archive from the in-memory data
|
||||
int archive_size = qop_open_data(buf, file_size, &qop_core);
|
||||
if (archive_size == 0) {
|
||||
printf("ERROR: Could not open QOP archive\n");
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Read the archive index
|
||||
qop_hashmap = malloc(qop_core.hashmap_size);
|
||||
if (!qop_hashmap) {
|
||||
printf("ERROR: Could not allocate memory for QOP hashmap\n");
|
||||
qop_close(&qop_core);
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int index_len = qop_read_index(&qop_core, qop_hashmap);
|
||||
if (index_len == 0) {
|
||||
printf("ERROR: Could not read QOP index\n");
|
||||
free(qop_hashmap);
|
||||
qop_hashmap = NULL;
|
||||
qop_close(&qop_core);
|
||||
free(buf);
|
||||
// Check if the core directory exists
|
||||
struct stat st;
|
||||
if (stat(core_path, &st) != 0 || !S_ISDIR(st.st_mode)) {
|
||||
printf("ERROR: Cell shop not found at %s/" CELL_SHOP_DIR "\n", home);
|
||||
printf("Run 'cell install' to set up the cell environment.\n");
|
||||
free(core_path);
|
||||
core_path = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Load a file from the core directory
|
||||
static char* load_core_file(const char *filename, size_t *out_size) {
|
||||
if (!core_path) return NULL;
|
||||
|
||||
size_t path_len = strlen(core_path) + 1 + strlen(filename) + 1;
|
||||
char *full_path = malloc(path_len);
|
||||
if (!full_path) return NULL;
|
||||
|
||||
snprintf(full_path, path_len, "%s/%s", core_path, filename);
|
||||
|
||||
FILE *fh = fopen(full_path, "rb");
|
||||
free(full_path);
|
||||
|
||||
if (!fh) return NULL;
|
||||
|
||||
fseek(fh, 0, SEEK_END);
|
||||
long file_size = ftell(fh);
|
||||
fseek(fh, 0, SEEK_SET);
|
||||
|
||||
char *data = malloc(file_size + 1);
|
||||
if (!data) {
|
||||
fclose(fh);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fread(data, 1, file_size, fh) != (size_t)file_size) {
|
||||
free(data);
|
||||
fclose(fh);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fclose(fh);
|
||||
data[file_size] = 0;
|
||||
|
||||
if (out_size) *out_size = file_size;
|
||||
return data;
|
||||
}
|
||||
|
||||
// Get the core path for use by scripts
|
||||
const char* cell_get_core_path(void) {
|
||||
return core_path;
|
||||
}
|
||||
|
||||
void actor_disrupt(cell_rt *crt)
|
||||
{
|
||||
crt->disrupt = 1;
|
||||
@@ -163,39 +160,28 @@ void script_startup(cell_rt *prt)
|
||||
crt->init_wota = NULL;
|
||||
}
|
||||
|
||||
// Store the core path for scripts to use
|
||||
JSValue js_cell = JS_GetPropertyStr(js, globalThis, "cell");
|
||||
JSValue hidden = JS_GetPropertyStr(js, js_cell, "hidden");
|
||||
size_t archive_size = qop_core.data_size - qop_core.files_offset;
|
||||
JSValue blob = js_new_blob_stoned_copy(js, qop_core.data + qop_core.files_offset, archive_size);
|
||||
JS_SetPropertyStr(js, hidden, "core_qop_blob", blob);
|
||||
if (core_path) {
|
||||
JS_SetPropertyStr(js, hidden, "core_path", JS_NewString(js, core_path));
|
||||
}
|
||||
JS_FreeValue(js, hidden);
|
||||
JS_FreeValue(js, js_cell);
|
||||
|
||||
JS_FreeValue(js, globalThis);
|
||||
|
||||
// Find and load engine.cm from QOP archive
|
||||
qop_file *engine_file = qop_find(&qop_core, ENGINE);
|
||||
if (!engine_file) {
|
||||
printf("ERROR: Could not find file %s in QOP archive!\n", ENGINE);
|
||||
return;
|
||||
}
|
||||
|
||||
char *data = malloc(engine_file->size + 1);
|
||||
// Load engine.cm from the core directory
|
||||
size_t engine_size;
|
||||
char *data = load_core_file(ENGINE, &engine_size);
|
||||
if (!data) {
|
||||
printf("ERROR: Could not allocate memory for %s!\n", ENGINE);
|
||||
printf("ERROR: Could not load %s from %s!\n", ENGINE, core_path);
|
||||
return;
|
||||
}
|
||||
|
||||
int bytes_read = qop_read(&qop_core, engine_file, (unsigned char *)data);
|
||||
if (bytes_read != (int)engine_file->size) {
|
||||
printf("ERROR: Could not read file %s from QOP archive!\n", ENGINE);
|
||||
free(data);
|
||||
return;
|
||||
}
|
||||
data[engine_file->size] = 0;
|
||||
|
||||
crt->state = ACTOR_RUNNING;
|
||||
JSValue v = JS_Eval(js, data, (size_t)engine_file->size, ENGINE, 0);
|
||||
JSValue v = JS_Eval(js, data, engine_size, ENGINE, 0);
|
||||
free(data);
|
||||
uncaught_exception(js, v);
|
||||
crt->state = ACTOR_IDLE;
|
||||
set_actor_state(crt);
|
||||
@@ -223,10 +209,9 @@ int cell_init(int argc, char **argv)
|
||||
{
|
||||
int script_start = 1;
|
||||
|
||||
/* Load QOP package attached to executable - this is now mandatory! */
|
||||
int mounted = prosperon_mount_core();
|
||||
if (!mounted) {
|
||||
printf("ERROR: Could not load core QOP package.\n");
|
||||
/* Find the cell shop at ~/.cell */
|
||||
int found = find_cell_shop();
|
||||
if (!found) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user