fix links, fix hot reload

This commit is contained in:
2025-12-10 01:37:21 -06:00
parent 37f2cff6ec
commit 752479e250
11 changed files with 526 additions and 240 deletions

View File

@@ -9,6 +9,10 @@
CELL_SHOP = $(HOME)/.cell CELL_SHOP = $(HOME)/.cell
CELL_CORE_PACKAGE = $(CELL_SHOP)/packages/core CELL_CORE_PACKAGE = $(CELL_SHOP)/packages/core
makecell:
cell pack core -o cell
cp cell /opt/homebrew/bin/
# Install core: symlink this directory to ~/.cell/core # Install core: symlink this directory to ~/.cell/core
install: bootstrap $(CELL_SHOP) install: bootstrap $(CELL_SHOP)
@echo "Linking cell core to $(CELL_CORE_PACKAGE)" @echo "Linking cell core to $(CELL_CORE_PACKAGE)"

View File

@@ -40,15 +40,6 @@ for (var i = 0; i < args.length; i++) {
log.error('-b requires a buildtype (release, debug, minsize)') log.error('-b requires a buildtype (release, debug, minsize)')
$_.stop() $_.stop()
} }
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') { } else if (args[i] == '--list-targets') {
log.console('Available targets:') log.console('Available targets:')
var targets = build.list_targets() var targets = build.list_targets()
@@ -105,8 +96,7 @@ if (target_package) {
} }
} }
log.console('') log.console(`Build complete: ${success} libraries built${failed > 0 ? `, ${failed} failed` : ''}`)
log.console('Build complete: ' + success + ' libraries built' + (failed > 0 ? ', ' + failed + ' failed' : ''))
} }
$_.stop() $_.stop()

View File

@@ -194,6 +194,7 @@ Build.build_package = function(pkg, target, exclude_main, buildtype = 'release')
// ============================================================================ // ============================================================================
// Build a dynamic library for a package // Build a dynamic library for a package
// Output goes to .cell/lib/<package_name>.<ext> // Output goes to .cell/lib/<package_name>.<ext>
// Dynamic libraries do NOT link against core; undefined symbols are resolved at dlopen time
Build.build_dynamic = function(pkg, target, buildtype = 'release') { Build.build_dynamic = function(pkg, target, buildtype = 'release') {
target = target || Build.detect_host_target() target = target || Build.detect_host_target()
@@ -222,15 +223,26 @@ Build.build_dynamic = function(pkg, target, buildtype = 'release') {
// Build link command // Build link command
var cmd_parts = [cc, '-shared', '-fPIC'] var cmd_parts = [cc, '-shared', '-fPIC']
// Add rpath to find libraries in .cell/local at runtime // Platform-specific flags for undefined symbols (resolved at dlopen) and size optimization
if (tc.system == 'darwin') { if (tc.system == 'darwin') {
// Allow undefined symbols - they will be resolved when dlopen'd into the main executable
cmd_parts.push('-undefined', 'dynamic_lookup')
// Dead-strip unused code
cmd_parts.push('-Wl,-dead_strip')
// rpath for .cell/local libraries
cmd_parts.push('-Wl,-rpath,@loader_path/../local') cmd_parts.push('-Wl,-rpath,@loader_path/../local')
cmd_parts.push('-Wl,-rpath,' + local_dir) cmd_parts.push('-Wl,-rpath,' + local_dir)
} else if (tc.system == 'linux') { } else if (tc.system == 'linux') {
// Allow undefined symbols at link time
cmd_parts.push('-Wl,--allow-shlib-undefined')
// Garbage collect unused sections
cmd_parts.push('-Wl,--gc-sections')
// rpath for .cell/local libraries
cmd_parts.push('-Wl,-rpath,$ORIGIN/../local') cmd_parts.push('-Wl,-rpath,$ORIGIN/../local')
cmd_parts.push('-Wl,-rpath,' + local_dir) cmd_parts.push('-Wl,-rpath,' + local_dir)
} else if (tc.system == 'windows') { } else if (tc.system == 'windows') {
// Windows uses PATH or same directory, add -L for link time // Windows DLLs: use --allow-shlib-undefined for mingw
cmd_parts.push('-Wl,--allow-shlib-undefined')
} }
// Add .cell/local to library search path // Add .cell/local to library search path
@@ -240,14 +252,7 @@ Build.build_dynamic = function(pkg, target, buildtype = 'release') {
cmd_parts.push('"' + objects[i] + '"') cmd_parts.push('"' + objects[i] + '"')
} }
// Link against core library (all dynamic libs depend on core) // Do NOT link against core library - symbols resolved at dlopen time
if (pkg != 'core') {
var core_lib_name = shop.lib_name_for_package('core')
var core_lib_path = lib_dir + '/' + core_lib_name + dylib_ext
if (fd.is_file(core_lib_path)) {
cmd_parts.push('"' + core_lib_path + '"')
}
}
// Add LDFLAGS (resolve relative -L paths) // Add LDFLAGS (resolve relative -L paths)
for (var i = 0; i < ldflags.length; i++) { for (var i = 0; i < ldflags.length; i++) {
@@ -375,7 +380,7 @@ Build.build_all_dynamic = function(target, buildtype = 'release') {
// Build core first // Build core first
if (packages.indexOf('core') >= 0) { if (packages.indexOf('core') >= 0) {
try { try {
var lib = Build.build_dynamic('core', target) var lib = Build.build_dynamic('core', target, buildtype)
results.push({ package: 'core', library: lib }) results.push({ package: 'core', library: lib })
} catch (e) { } catch (e) {
log.error('Failed to build core: ' + e) log.error('Failed to build core: ' + e)
@@ -389,7 +394,7 @@ Build.build_all_dynamic = function(target, buildtype = 'release') {
if (pkg == 'core') continue if (pkg == 'core') continue
try { try {
var lib = Build.build_dynamic(pkg, target) var lib = Build.build_dynamic(pkg, target, buildtype)
results.push({ package: pkg, library: lib }) results.push({ package: pkg, library: lib })
} catch (e) { } catch (e) {
log.error('Failed to build ' + pkg + ': ') log.error('Failed to build ' + pkg + ': ')

View File

@@ -279,7 +279,7 @@ function mount_package(name) {
} }
var shop = use('shop') var shop = use('shop')
var dir = shop.get_module_dir(name) var dir = shop.get_package_dir(name)
if (!dir) { if (!dir) {
throw new Error("Package not found: " + name) throw new Error("Package not found: " + name)

View File

@@ -215,6 +215,7 @@ var $_ = create_actor()
os.use_cache = use_cache os.use_cache = use_cache
os.global_shop_path = shop_path os.global_shop_path = shop_path
os.$_ = $_
var shop = use('shop') var shop = use('shop')

164
link.ce
View File

@@ -1,15 +1,18 @@
// link <command> [args] // link <command> [args]
// Commands: // Commands:
// list // list List all active links
// delete <alias> // sync Ensure all symlinks are in place
// clear // delete <package> Remove a link
// [package] <path|target> // clear Remove all links
// <path> Link the package in <path> to that path
// <package> <target> Link <package> to <target> (path or another package)
// //
// Examples: // Examples:
// cell link ../cell-steam (Links package defined in ../cell-steam to that path) // cell link ../cell-steam (Links package in ../cell-steam)
// cell link my-pkg ../cell-steam (Links my-pkg to ../cell-steam) // cell link gitea.pockle.world/john/prosperon ../prosperon (Links prosperon to local path)
// cell link my-pkg other-pkg (Links my-pkg to other-pkg) // cell link gitea.pockle.world/john/prosperon github.com/prosperon (Links to another remote)
var link = use('link')
var shop = use('shop') var shop = use('shop')
var fd = use('fd') var fd = use('fd')
var toml = use('toml') var toml = use('toml')
@@ -18,10 +21,11 @@ if (args.length < 1) {
log.console("Usage: link <command> [args] or link [package] <target>") log.console("Usage: link <command> [args] or link [package] <target>")
log.console("Commands:") log.console("Commands:")
log.console(" list List all active links") log.console(" list List all active links")
log.console(" delete <alias> Remove a link") log.console(" sync Ensure all symlinks are in place")
log.console(" delete <package> Remove a link and restore original")
log.console(" clear Remove all links") log.console(" clear Remove all links")
log.console(" <path> Link the package in <path> to <path>") log.console(" <path> Link the package in <path> to that path")
log.console(" <alias> <target> Link <alias> to <target> (path or package)") log.console(" <package> <target> Link <package> to <target> (path or package)")
$_.stop() $_.stop()
return return
} }
@@ -29,38 +33,57 @@ if (args.length < 1) {
var cmd = args[0] var cmd = args[0]
if (cmd == 'list') { if (cmd == 'list') {
var links = shop.load_links() var links = link.load()
var count = 0 var count = 0
for (var k in links) { for (var k in links) {
log.console(k + " -> " + links[k]) log.console(k + " -> " + links[k])
count++ count++
} }
if (count == 0) log.console("No links.") if (count == 0) log.console("No links.")
} else if (cmd == 'sync') {
log.console("Syncing links...")
var result = link.sync_all(shop)
log.console("Synced " + result.synced + " link(s)")
if (result.errors.length > 0) {
log.console("Errors:")
for (var i = 0; i < result.errors.length; i++) {
log.console(" " + result.errors[i])
}
}
} else if (cmd == 'delete' || cmd == 'rm') { } else if (cmd == 'delete' || cmd == 'rm') {
if (args.length < 2) { if (args.length < 2) {
log.console("Usage: link delete <alias>") log.console("Usage: link delete <package>")
$_.stop() $_.stop()
return return
} }
var target = args[1] var pkg = args[1]
// Try to remove directly if (link.remove(pkg)) {
if (shop.remove_link(target)) { // Try to restore the original package
// Update to restore original if possible log.console("Restoring " + pkg + "...")
log.console("Restoring " + target + "...") try {
shop.update(target) shop.fetch(pkg)
shop.extract(pkg)
log.console("Restored " + pkg)
} catch (e) {
log.console("Could not restore: " + e.message)
log.console("Run 'cell update " + pkg + "' to restore")
}
} else { } else {
log.console("No link found for " + target) log.console("No link found for " + pkg)
} }
} else if (cmd == 'clear') { } else if (cmd == 'clear') {
shop.clear_links() link.clear()
log.console("Links cleared. Run 'cell update' to restore packages if needed.") log.console("Links cleared. Run 'cell update' to restore packages.")
} else { } else {
// Linking logic // Linking logic
var pkg_name = null var pkg_name = null
var target_path = null var target = null
// Check for 'add' compatibility // Check for 'add' compatibility
var start_idx = 0 var start_idx = 0
@@ -68,18 +91,11 @@ if (cmd == 'list') {
start_idx = 1 start_idx = 1
} }
// Parse arguments
// usage: link [pkg] <target>
// valid inputs:
// link ../foo
// link pkg ../foo
// link pkg remote-pkg
var arg1 = args[start_idx] var arg1 = args[start_idx]
var arg2 = (args.length > start_idx + 1) ? args[start_idx + 1] : null var arg2 = (args.length > start_idx + 1) ? args[start_idx + 1] : null
if (!arg1) { if (!arg1) {
log.console("Error: specific target or package required") log.console("Error: target or package required")
$_.stop() $_.stop()
return return
} }
@@ -87,34 +103,50 @@ if (cmd == 'list') {
if (arg2) { if (arg2) {
// Two arguments: explicit package name and target // Two arguments: explicit package name and target
pkg_name = arg1 pkg_name = arg1
target_path = arg2 target = arg2
// Resolve target if it's a local path
if (target == '.' || fd.is_dir(target)) {
target = fd.realpath(target)
} else if (target.startsWith('./') || target.startsWith('../')) {
// Relative path that doesn't exist yet - try to resolve anyway
var cwd = fd.realpath('.')
if (target.startsWith('./')) {
target = cwd + target.substring(1)
} else { } else {
// One argument: assume it's a path, and we need to infer the package name // For ../ paths, let fd.realpath handle it if possible
target_path = arg1 target = fd.realpath(target) || target
}
}
// Otherwise target is a package name (e.g., github.com/prosperon)
// Resolve path to check for cell.toml } else {
var resolved = target_path // One argument: assume it's a local path, infer package name from cell.toml
if (fd.is_dir(resolved)) resolved = fd.realpath(resolved) target = arg1
var toml_path = resolved + '/cell.toml' // Resolve path
if (target == '.' || fd.is_dir(target)) {
target = fd.realpath(target)
} else if (target.startsWith('./') || target.startsWith('../')) {
target = fd.realpath(target) || target
}
// Must be a local path with cell.toml
var toml_path = target + '/cell.toml'
if (!fd.is_file(toml_path)) { if (!fd.is_file(toml_path)) {
log.console("Error: No cell.toml found at " + resolved + ". Cannot infer package name.") log.console("Error: No cell.toml found at " + target)
log.console("If you meant to link an alias, provide the target: link <alias> <target>") log.console("For linking to another package, use: link <package> <target>")
$_.stop() $_.stop()
return return
} }
// Read package name // Read package name from cell.toml
try { try {
var content = toml.decode(text(fd.slurp(toml_path))) var content = toml.decode(text(fd.slurp(toml_path)))
if (content.package) { if (content.package) {
pkg_name = content.package pkg_name = content.package
// If package name has version, strip it?
// Usually package = "name", version = "..." in cell.toml?
// Or package = "name"
// Standard is just name.
} else { } else {
log.console("Error: cell.toml at " + resolved + " does not define a 'package' name.") log.console("Error: cell.toml at " + target + " does not define 'package'")
$_.stop() $_.stop()
return return
} }
@@ -123,39 +155,25 @@ if (cmd == 'list') {
$_.stop() $_.stop()
return return
} }
// Ensure target path is fully resolved since we inferred it
if (fd.is_dir(target_path)) target_path = fd.realpath(target_path)
} }
// Process the link // Validate: if target is a local path, it must have cell.toml
if (target.startsWith('/')) {
// 1. Resolve target if it is a directory if (!fd.is_file(target + '/cell.toml')) {
if (target_path != "." && fd.is_dir(target_path)) { log.console("Error: " + target + " is not a valid package (no cell.toml)")
target_path = fd.realpath(target_path) $_.stop()
} else if (target_path == ".") { return
target_path = fd.realpath(target_path)
} else if (target_path.startsWith('/') || target_path.startsWith('./') || target_path.startsWith('../')) {
// It looks like a path but doesn't exist?
log.console("Warning: Link target '" + target_path + "' does not exist locally. Linking as alias anyway.")
if (target_path.startsWith('./') || target_path.startsWith('../')) {
// Resolve relative path roughly?
var cwd = fd.realpath('.')
// simple concat (fd.realpath typically converts to abspath, but if file missing it might fail or return same)
// Assuming user wants strict path if they used relative.
// Using fd.realpath on CWD + path is safer if we want absolute.
// But we don't have path manipulation lib easily exposed except implicit logic.
// Leaving as provided string if not existing dir.
} }
} }
// 2. Add link // Add the link (this also creates the symlink)
shop.add_link(pkg_name, target_path) try {
link.add(pkg_name, target, shop)
// 3. Update shop } catch (e) {
// "Doing this effectively adds another item to the shop" log.console("Error: " + e.message)
// We trigger update to ensure the shop recognizes the new link $_.stop()
shop.update(pkg_name) return
}
} }
$_.stop() $_.stop()

219
link.cm Normal file
View File

@@ -0,0 +1,219 @@
// Link management module for cell packages
// Handles creating, removing, and syncing symlinks for local development
var toml = use('toml')
var fd = use('fd')
var utf8 = use('utf8')
var os = use('os')
var global_shop_path = os.global_shop_path
// Get the links file path (in the global shop)
function get_links_path() {
return global_shop_path + '/link.toml'
}
// Get the packages directory (in the global shop)
function get_packages_dir() {
return global_shop_path + '/packages'
}
// return the safe path for the package
function safe_package_path(pkg) {
return pkg.replaceAll('@', '_')
}
function get_package_abs_dir(package) {
return get_packages_dir() + '/' + package
}
function ensure_dir(path) {
if (fd.stat(path).isDirectory) return
var parts = path.split('/')
var current = path.startsWith('/') ? '/' : ''
for (var i = 0; i < parts.length; i++) {
if (parts[i] == '') continue
current += parts[i] + '/'
if (!fd.stat(current).isDirectory) {
fd.mkdir(current)
}
}
}
// Resolve a link target to its actual path
// If target is a local path (starts with /), return it directly
// If target is a package name, return the package directory
function resolve_link_target(target) {
if (target.startsWith('/')) {
return target
}
// Target is another package - resolve to its directory
return get_packages_dir() + '/' + safe_package_path(target)
}
var Link = {}
var link_cache = null
Link.load = function() {
if (link_cache) return link_cache
var path = get_links_path()
if (!fd.is_file(path)) {
link_cache = {}
return link_cache
}
try {
var content = text(fd.slurp(path))
var cfg = toml.decode(content)
link_cache = cfg.links || {}
} catch (e) {
log.console("Warning: Failed to load link.toml: " + e)
link_cache = {}
}
return link_cache
}
Link.save = function(links) {
link_cache = links
var cfg = { links: links }
var path = get_links_path()
fd.slurpwrite(path, utf8.encode(toml.encode(cfg)))
}
Link.add = function(canonical, target, shop) {
// Validate canonical package exists in shop
var lock = shop.load_lock()
if (!lock[canonical]) {
throw new Error('Package ' + canonical + ' is not installed. Install it first with: cell get ' + canonical)
}
// Validate target is a valid package
if (target.startsWith('/')) {
// Local path - must have cell.toml
if (!fd.is_file(target + '/cell.toml')) {
throw new Error('Target ' + target + ' is not a valid package (no cell.toml)')
}
} else {
// Remote package target - ensure it's installed
shop.get(target)
}
var links = Link.load()
links[canonical] = target
Link.save(links)
// Create the symlink immediately
Link.sync_one(canonical, target, shop)
log.console("Linked " + canonical + " -> " + target)
return true
}
Link.remove = function(canonical) {
var links = Link.load()
if (!links[canonical]) return false
// Remove the symlink if it exists
var target_dir = get_package_abs_dir(canonical)
if (fd.is_link(target_dir)) {
fd.unlink(target_dir)
log.console("Removed symlink at " + target_dir)
}
delete links[canonical]
Link.save(links)
log.console("Unlinked " + canonical)
return true
}
Link.clear = function() {
// Remove all symlinks first
var links = Link.load()
for (var canonical in links) {
var target_dir = get_package_abs_dir(canonical)
if (fd.is_link(target_dir)) {
fd.unlink(target_dir)
}
}
Link.save({})
log.console("Cleared all links")
return true
}
// Sync a single link - ensure the symlink is in place
Link.sync_one = function(canonical, target, shop) {
var target_dir = get_package_abs_dir(canonical)
var link_target = resolve_link_target(target)
// Ensure parent directories exist
var parent = target_dir.substring(0, target_dir.lastIndexOf('/'))
ensure_dir(parent)
// Check current state
var current_link = null
if (fd.is_link(target_dir)) {
current_link = fd.readlink(target_dir)
}
// If already correctly linked, nothing to do
if (current_link == link_target) {
return true
}
// Remove existing file/dir/link
if (fd.is_link(target_dir)) {
fd.unlink(target_dir)
} else if (fd.is_dir(target_dir)) {
fd.rmdir(target_dir, 1)
}
// Create symlink
fd.symlink(link_target, target_dir)
return true
}
// Sync all links - ensure all symlinks are in place
Link.sync_all = function(shop) {
var links = Link.load()
var count = 0
var errors = []
for (var canonical in links) {
var target = links[canonical]
try {
// Validate target exists
var link_target = resolve_link_target(target)
if (!fd.is_dir(link_target)) {
errors.push(canonical + ': target ' + link_target + ' does not exist')
continue
}
if (!fd.is_file(link_target + '/cell.toml')) {
errors.push(canonical + ': target ' + link_target + ' is not a valid package')
continue
}
Link.sync_one(canonical, target, shop)
count++
} catch (e) {
errors.push(canonical + ': ' + e.message)
}
}
return { synced: count, errors: errors }
}
// Check if a package is currently linked
Link.is_linked = function(canonical) {
var links = Link.load()
return canonical in links
}
// Get the link target for a package (or null if not linked)
Link.get_target = function(canonical) {
var links = Link.load()
return links[canonical] || null
}
return Link

View File

@@ -41,6 +41,13 @@ package.find_alias = function(name, locator)
return null return null
} }
package.alias_to_package = function(name, alias)
{
var config = package.load_config(name)
if (!config.dependencies) return null
return config.dependencies[alias]
}
// alias is optional // alias is optional
package.add_dependency = function(name, locator, alias = locator) package.add_dependency = function(name, locator, alias = locator)
{ {

214
shop.cm
View File

@@ -10,6 +10,7 @@ var utf8 = use('utf8')
var blob = use('blob') var blob = use('blob')
var pkg_tools = use('package') var pkg_tools = use('package')
var os = use('os') var os = use('os')
var link = use('link')
var core = "core" var core = "core"
@@ -58,10 +59,15 @@ var SCOPE_CORE = 2
var MOD_EXT = '.cm' var MOD_EXT = '.cm'
var ACTOR_EXT = '.ce' var ACTOR_EXT = '.ce'
var dylib_ext = '.so' // Default extension var dylib_ext = '.dylib' // Default extension
var use_cache = os.use_cache var use_cache = os.use_cache
var global_shop_path = os.global_shop_path var global_shop_path = os.global_shop_path
var $_ = os.$_
Shop.get_package_dir = function(name) {
return global_shop_path + '/packages/' + name
}
// Get the packages directory (in the global shop) // Get the packages directory (in the global shop)
function get_packages_dir() { function get_packages_dir() {
@@ -85,8 +91,6 @@ Shop.get_reports_dir = function() {
return global_shop_path + '/reports' return global_shop_path + '/reports'
} }
var open_dl = {}
function get_import_package(name) { function get_import_package(name) {
var parts = name.split('/') var parts = name.split('/')
if (parts.length > 1) if (parts.length > 1)
@@ -113,6 +117,16 @@ function abs_path_to_package(package_dir)
if (package_in_shop(package_dir)) if (package_in_shop(package_dir))
return package_dir return package_dir
// For local directories (e.g., linked targets), read the package name from cell.toml
try {
var content = text(fd.slurp(package_dir + '/cell.toml'))
var cfg = toml.decode(content)
if (cfg.package)
return cfg.package
} catch (e) {
// Fall through
}
return null return null
} }
@@ -138,9 +152,9 @@ Shop.file_info = function(file) {
info.name = file.substring(pkg_dir.length + 1) info.name = file.substring(pkg_dir.length + 1)
if (info.is_actor) if (info.is_actor)
info.name = info.name.substring(0, info.name.length - ACTOR_EXT.length) info.name = info.path.substring(0, info.path.length - ACTOR_EXT.length)
else if (info.is_module) else if (info.is_module)
info.name = info.name.substring(0, info.name.length - MOD_EXT.length) info.name = info.path.substring(0, info.path.length - MOD_EXT.length)
} }
return info return info
@@ -212,56 +226,6 @@ Shop.save_lock = function(lock) {
fd.slurpwrite(path, utf8.encode(toml.encode(lock))); fd.slurpwrite(path, utf8.encode(toml.encode(lock)));
} }
/* links functionality */
var link_cache = null
Shop.load_links = function() {
if (link_cache) return link_cache
var path = global_shop_path + '/link.toml'
if (!fd.is_file(path)) {
link_cache = {}
return link_cache
}
try {
var content = text(fd.slurp(path))
var cfg = toml.decode(content)
link_cache = cfg.links || {}
} catch (e) {
log.console("Warning: Failed to load link.toml: " + e)
link_cache = {}
}
return link_cache
}
Shop.save_links = function(links) {
link_cache = links
var cfg = { links: links }
var path = global_shop_path + '/link.toml'
fd.slurpwrite(path, utf8.encode(toml.encode(cfg)))
}
Shop.add_link = function(canonical, target) {
var links = Shop.load_links()
links[canonical] = target
Shop.save_links(links)
log.console("Linked " + canonical + " -> " + target)
return true
}
Shop.remove_link = function(canonical) {
var links = Shop.load_links()
if (!links[canonical]) return false
delete links[canonical]
Shop.save_links(links)
log.console("Unlinked " + canonical)
return true
}
Shop.clear_links = function() {
Shop.save_links({})
log.console("Cleared all links")
return true
}
// Get information about how to resolve a package // Get information about how to resolve a package
// Local packages always start with / // Local packages always start with /
@@ -334,8 +298,8 @@ var open_dls = {}
// for script forms, path is the canonical path of the module // for script forms, path is the canonical path of the module
var script_form = function(path, script, pkg) { var script_form = function(path, script, pkg) {
var pkg_arg = pkg ? `'${pkg}'` : 'null' var pkg_arg = pkg ? `'${pkg}'` : 'null'
var relative_use_fn = `def use = function(path) { return globalThis.use(path, ${pkg_arg});}` var relative_use_fn = `def PACKAGE = ${pkg_arg}; def use = function(path) { return globalThis.use(path, ${pkg_arg});}`
var fn = `(function setup_module($_, args){ ${relative_use_fn}; ${script}})` var fn = `(function setup_module($_, args){ def arg = args; ${relative_use_fn}; ${script}})`
return fn return fn
} }
@@ -344,8 +308,10 @@ var script_form = function(path, script, pkg) {
function resolve_mod_fn(path, pkg) { function resolve_mod_fn(path, pkg) {
if (!fd.is_file(path)) throw new Error(`path ${path} is not a file`) if (!fd.is_file(path)) throw new Error(`path ${path} is not a file`)
var file_info = Shop.file_info(path)
var file_pkg = file_info.package
var content = text(fd.slurp(path)) var content = text(fd.slurp(path))
var script = script_form(path, content, pkg); var script = script_form(path, content, file_pkg);
var obj = pull_from_cache(utf8.encode(script)) var obj = pull_from_cache(utf8.encode(script))
if (obj) { if (obj) {
@@ -415,24 +381,48 @@ function resolve_locator(path, ctx)
// Generate symbol name for a C module file // Generate symbol name for a C module file
// Uses the same format as Shop.c_symbol_for_file // Uses the same format as Shop.c_symbol_for_file
// Resolves linked packages to their actual target first
function make_c_symbol(pkg, file) { function make_c_symbol(pkg, file) {
var pkg_safe = pkg.replace(/\//g, '_').replace(/\./g, '_').replace(/-/g, '_') // Check if this package is linked - if so, use the link target for symbol name
var link_target = link.get_target(pkg)
var resolved_pkg = link_target ? link_target : pkg
var pkg_safe = resolved_pkg.replace(/\//g, '_').replace(/\./g, '_').replace(/-/g, '_')
var file_safe = file.replace(/\//g, '_').replace(/\./g, '_').replace(/-/g, '_') var file_safe = file.replace(/\//g, '_').replace(/\./g, '_').replace(/-/g, '_')
return 'js_' + pkg_safe + '_' + file_safe + '_use' return 'js_' + pkg_safe + '_' + file_safe + '_use'
} }
// Get the library path for a package in .cell/lib // Get the library path for a package in .cell/lib
// Resolves linked packages to their actual target first
function get_lib_path(pkg) { function get_lib_path(pkg) {
var lib_name = pkg.replace(/\//g, '_').replace(/\./g, '_').replace(/-/g, '_') // Check if this package is linked - if so, use the link target
var link_target = link.get_target(pkg)
var resolved_pkg = link_target ? link_target : pkg
var lib_name = resolved_pkg.replace(/\//g, '_').replace(/\./g, '_').replace(/-/g, '_')
return global_shop_path + '/lib/' + lib_name + dylib_ext return global_shop_path + '/lib/' + lib_name + dylib_ext
} }
// Resolve a C symbol by searching:
// 1. If package_context is null, only check core internal symbols
// 2. Otherwise: own package (internal then dylib) -> other packages (internal then dylib) -> core (internal only)
// Core is never loaded as a dynamic library via dlopen
function resolve_c_symbol(path, package_context) function resolve_c_symbol(path, package_context)
{ {
var static_only = cell.static_only // If no package context, only check core internal symbols
if (!package_context) {
var core_sym = `js_${path}_use`
if (os.internal_exists(core_sym)) {
return {
symbol: function() { return os.load_internal(core_sym) },
scope: SCOPE_CORE,
path: core_sym
}
}
return null
}
// 1. Check internal symbols (statically linked) in package context // 1. Check own package first (internal, then dylib)
if (package_context) {
var sym = make_c_symbol(package_context, path) var sym = make_c_symbol(package_context, path)
if (os.internal_exists(sym)) { if (os.internal_exists(sym)) {
return { return {
@@ -443,6 +433,7 @@ function resolve_c_symbol(path, package_context)
} }
var dl_path = get_lib_path(package_context) var dl_path = get_lib_path(package_context)
if (fd.is_file(dl_path)) { if (fd.is_file(dl_path)) {
if (!open_dls[dl_path]) open_dls[dl_path] = os.dylib_open(dl_path) if (!open_dls[dl_path]) open_dls[dl_path] = os.dylib_open(dl_path)
if (open_dls[dl_path] && os.dylib_has_symbol(open_dls[dl_path], sym)) { if (open_dls[dl_path] && os.dylib_has_symbol(open_dls[dl_path], sym)) {
@@ -453,9 +444,8 @@ function resolve_c_symbol(path, package_context)
} }
} }
} }
}
// 2. Check if valid package import (e.g. 'prosperon/sprite') // 2. Check aliased package imports (e.g. 'prosperon/sprite')
var pkg_alias = get_import_package(path) var pkg_alias = get_import_package(path)
if (pkg_alias) { if (pkg_alias) {
var canon_pkg = get_aliased_package(path, package_context) var canon_pkg = get_aliased_package(path, package_context)
@@ -463,6 +453,7 @@ function resolve_c_symbol(path, package_context)
var mod_name = get_import_name(path) var mod_name = get_import_name(path)
var sym = make_c_symbol(canon_pkg, mod_name) var sym = make_c_symbol(canon_pkg, mod_name)
// Check internal first
if (os.internal_exists(sym)) { if (os.internal_exists(sym)) {
return { return {
symbol: function() { return os.load_internal(sym) }, symbol: function() { return os.load_internal(sym) },
@@ -472,6 +463,7 @@ function resolve_c_symbol(path, package_context)
} }
} }
// Then check dylib
var dl_path = get_lib_path(canon_pkg) var dl_path = get_lib_path(canon_pkg)
if (fd.is_file(dl_path)) { if (fd.is_file(dl_path)) {
if (!open_dls[dl_path]) open_dls[dl_path] = os.dylib_open(dl_path) if (!open_dls[dl_path]) open_dls[dl_path] = os.dylib_open(dl_path)
@@ -487,7 +479,7 @@ function resolve_c_symbol(path, package_context)
} }
} }
// 3. Check core fallback // 3. Check core internal symbols (core is never a dynamic library)
var core_sym = `js_${path}_use` var core_sym = `js_${path}_use`
if (os.internal_exists(core_sym)) { if (os.internal_exists(core_sym)) {
return { return {
@@ -497,21 +489,6 @@ function resolve_c_symbol(path, package_context)
} }
} }
// Try core dynamic library
if (!static_only) {
var core_dl = get_lib_path('core')
if (fd.is_file(core_dl)) {
if (!open_dls[core_dl]) open_dls[core_dl] = os.dylib_open(core_dl)
if (open_dls[core_dl] && os.dylib_has_symbol(open_dls[core_dl], core_sym)) {
return {
symbol: function() { return os.dylib_symbol(open_dls[core_dl], core_sym) },
scope: SCOPE_CORE,
path: core_sym
}
}
}
}
return null return null
} }
@@ -523,9 +500,27 @@ function resolve_module_info(path, package_context) {
if (min_scope == 999) if (min_scope == 999)
return null return null
// Cache key is now package/file format // Cache key is based on the realpath of the resolved file
// e.g., "core/shop", "gitea.pockle.world/john/prosperon/sprite" // This ensures linked packages resolve to the same cache entry
// whether accessed via symlink or directly
var cache_key var cache_key
if (mod_resolve.scope < 900 && mod_resolve.path) {
// Use realpath to resolve symlinks and get the actual file location
var real_path = fd.realpath(mod_resolve.path)
if (real_path) {
// Derive cache key from the real path's package info
var real_info = Shop.file_info(real_path)
if (real_info.package && real_info.name) {
cache_key = real_info.package + '/' + real_info.name
} else {
// Fallback to the realpath itself
cache_key = real_path
}
}
}
// Fallback for C-only modules or if realpath failed
if (!cache_key) {
if (min_scope == SCOPE_CORE) { if (min_scope == SCOPE_CORE) {
cache_key = 'core/' + path cache_key = 'core/' + path
} else if (min_scope == SCOPE_LOCAL && package_context) { } else if (min_scope == SCOPE_LOCAL && package_context) {
@@ -547,6 +542,7 @@ function resolve_module_info(path, package_context) {
} else { } else {
cache_key = path cache_key = path
} }
}
return { return {
cache_key: cache_key, cache_key: cache_key,
@@ -570,7 +566,6 @@ function execute_module(info)
{ {
var c_resolve = info.c_resolve var c_resolve = info.c_resolve
var mod_resolve = info.mod_resolve var mod_resolve = info.mod_resolve
var cache_key = info.cache_key
var used var used
@@ -607,7 +602,6 @@ Shop.use = function(path, package_context) {
if (use_cache[info.cache_key]) if (use_cache[info.cache_key])
return use_cache[info.cache_key] return use_cache[info.cache_key]
use_cache[info.cache_key] = execute_module(info) use_cache[info.cache_key] = execute_module(info)
return use_cache[info.cache_key] return use_cache[info.cache_key]
} }
@@ -709,13 +703,23 @@ Shop.fetch = function(pkg) {
} }
// Extract: Extract a package to its target directory // Extract: Extract a package to its target directory
// For local packages, creates a symlink // For linked packages, creates a symlink to the link target
// For local packages, creates a symlink to the local path
// For remote packages, extracts from the provided zip blob // For remote packages, extracts from the provided zip blob
// Returns true on success // Returns true on success
Shop.extract = function(pkg) { Shop.extract = function(pkg) {
var info = Shop.resolve_package_info(pkg)
var target_dir = get_package_abs_dir(pkg) var target_dir = get_package_abs_dir(pkg)
// Check if this package is linked
var link_target = link.get_target(pkg)
if (link_target) {
// Use the link - create symlink to link target
link.sync_one(pkg, link_target)
return true
}
var info = Shop.resolve_package_info(pkg)
if (info == 'local') { if (info == 'local') {
if (fd.is_link(target_dir)) if (fd.is_link(target_dir))
fd.unlink(target_dir) fd.unlink(target_dir)
@@ -831,11 +835,47 @@ Shop.remove = function(pkg) {
return true return true
} }
Shop.get = function(pkg) {
var lock = Shop.load_lock()
if (!lock[pkg]) {
var info = Shop.resolve_package_info(pkg)
if (!info) {
throw new Error("Invalid package: " + pkg)
}
var commit = null
if (info != 'local') {
commit = fetch_remote_hash(pkg)
if (!commit) {
throw new Error("Could not resolve commit for " + pkg)
}
}
lock[pkg] = {
type: info,
commit: commit,
updated: time.number()
}
Shop.save_lock(lock)
}
}
// Compile a module // Compile a module
// List all files in a package // List all files in a package
var debug = use('debug') var debug = use('debug')
Shop.file_reload = function(file)
{
var info = Shop.file_info(file)
if (!info.is_module) return
var pkg = info.package
Shop.module_reload(info.name, pkg)
}
Shop.module_reload = function(path, package) { Shop.module_reload = function(path, package) {
if (!Shop.is_loaded(path,package)) return if (!Shop.is_loaded(path,package)) return
var info = resolve_module_info(path, package) var info = resolve_module_info(path, package)

View File

@@ -900,7 +900,7 @@ static JSValue js_error_toString(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv); int argc, JSValueConst *argv);
static const JSClassExoticMethods js_string_exotic_methods; static const JSClassExoticMethods js_string_exotic_methods;
static JSClassID js_class_id_alloc = JS_CLASS_INIT_COUNT; JSClassID js_class_id_alloc = JS_CLASS_INIT_COUNT;
static void js_trigger_gc(JSRuntime *rt, size_t size) static void js_trigger_gc(JSRuntime *rt, size_t size)
{ {

View File

@@ -496,6 +496,8 @@ JSClassID JS_GetClassID(JSValue v);
int JS_NewClass(JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def); int JS_NewClass(JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def);
int JS_IsRegisteredClass(JSRuntime *rt, JSClassID class_id); int JS_IsRegisteredClass(JSRuntime *rt, JSClassID class_id);
extern JSClassID js_class_id_alloc;
/* value handling */ /* value handling */
static js_force_inline JSValue JS_NewBool(JSContext *ctx, JS_BOOL val) static js_force_inline JSValue JS_NewBool(JSContext *ctx, JS_BOOL val)