From 601a78b3c7570a6186b643847cdad7a96c3a39cb Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Fri, 20 Feb 2026 14:10:24 -0600 Subject: [PATCH] package resolution --- internal/engine.cm | 31 +++++++++++++--- internal/shop.cm | 90 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 104 insertions(+), 17 deletions(-) diff --git a/internal/engine.cm b/internal/engine.cm index 7c279d3c..853ea881 100644 --- a/internal/engine.cm +++ b/internal/engine.cm @@ -1224,7 +1224,18 @@ if (prog_info) { } $_.clock(_ => { - var file_info = shop.file_info ? shop.file_info(prog_path) : null + var _file_info_ok = false + var file_info = null + var _try_fi = function() { + file_info = shop.file_info ? shop.file_info(prog_path) : null + _file_info_ok = true + } disruption {} + _try_fi() + if (!_file_info_ok || !file_info) + file_info = {path: prog_path, is_module: false, is_actor: true, package: null, name: prog} + // If the unified resolver found the package, use that as the authoritative source + if (prog_info && prog_info.pkg) + file_info.package = prog_info.pkg var inject = shop.script_inject_for ? shop.script_inject_for(file_info) : [] // Build env with runtime functions + capability injections @@ -1278,10 +1289,22 @@ $_.clock(_ => { env.use = function(path) { var ck = 'core/' + path + var _use_core_result = null + var _use_core_ok = false if (use_cache[ck]) return use_cache[ck] - var core_mod = use_core(path) - if (core_mod) return core_mod - return shop.use(path, pkg) + var _try_core = function() { + _use_core_result = use_core(path) + _use_core_ok = true + } disruption {} + _try_core() + if (_use_core_ok && _use_core_result) return _use_core_result + var _shop_use = function() { + return shop.use(path, pkg) + } disruption { + log.error(`use('${path}') failed (package: ${pkg})`) + disrupt + } + return _shop_use() } env.args = _cell.args.arg env.log = log diff --git a/internal/shop.cm b/internal/shop.cm index c9d6b657..ceea81ea 100644 --- a/internal/shop.cm +++ b/internal/shop.cm @@ -137,9 +137,6 @@ function split_explicit_package_import(path) if (package_in_shop(pkg_candidate)) return {package: pkg_candidate, path: mod_path} - - if (Shop.resolve_package_info(pkg_candidate)) - return {package: pkg_candidate, path: mod_path} } return null @@ -158,6 +155,8 @@ function abs_path_to_package(package_dir) } var packages_prefix = get_packages_dir() + '/' + var packages_prefix_abs = fd.realpath(get_packages_dir()) + if (packages_prefix_abs) packages_prefix_abs = packages_prefix_abs + '/' var core_dir = packages_prefix + core_package // Check if this is the core package directory (or its symlink target) @@ -176,6 +175,10 @@ function abs_path_to_package(package_dir) if (starts_with(package_dir, packages_prefix)) return text(package_dir, length(packages_prefix)) + // Also try absolute path comparison (package_dir may be absolute, packages_prefix relative) + if (packages_prefix_abs && starts_with(package_dir, packages_prefix_abs)) + return text(package_dir, length(packages_prefix_abs)) + // Check if this local path is the target of a link // If so, return the canonical package name (link origin) instead var link_origin = link.get_origin(package_dir) @@ -344,9 +347,11 @@ function get_policy() { // Get information about how to resolve a package // Local packages always start with / +// Remote packages must be exactly host/owner/repo (3 components) Shop.resolve_package_info = function(pkg) { if (starts_with(pkg, '/')) return 'local' - if (search(pkg, 'gitea') != null) return 'gitea' + var parts = array(pkg, '/') + if (length(parts) == 3 && search(parts[0], 'gitea') != null) return 'gitea' return null } @@ -1349,25 +1354,78 @@ Shop.resolve_program = function(prog, package_context) { }) // If not in lock, check if this looks like a fetchable package - if (!best_pkg) { - for (i = length(parts) - 1; i >= 1; i--) { - candidate = text(array(parts, 0, i), '/') - pkg_info = Shop.resolve_package_info(candidate) - if (pkg_info && pkg_info != 'local') { - best_pkg = candidate - best_remainder = text(array(parts, i), '/') - break - } + // For gitea-style URLs, the package root is host/owner/repo (3 components) + if (!best_pkg && length(parts) > 3) { + candidate = text(array(parts, 0, 3), '/') + pkg_info = Shop.resolve_package_info(candidate) + if (pkg_info && pkg_info != 'local') { + best_pkg = candidate + best_remainder = text(array(parts, 3), '/') } } if (best_pkg && best_remainder) { log.console('fetching ' + best_pkg + '...') _auto = function() { + // Install the package itself first Shop.update(best_pkg) Shop.fetch(best_pkg) Shop.extract(best_pkg) + // Install dependencies iteratively (each dep must be extracted before reading its deps) + var all_deps = {} + var queue = [best_pkg] + var qi = 0 + var current = null + var direct_deps = null + var dep_locator = null + var dep_dir = null + var build_mod = null + var target = null + var _build_c = null + var _read_deps = null + while (qi < length(queue)) { + current = queue[qi] + qi = qi + 1 + _read_deps = function() { + direct_deps = pkg_tools.dependencies(current) + } disruption { + direct_deps = null + } + _read_deps() + if (direct_deps) { + arrfor(array(direct_deps), function(alias) { + dep_locator = direct_deps[alias] + if (!all_deps[dep_locator]) { + all_deps[dep_locator] = true + dep_dir = pkg_tools.get_dir(dep_locator) + if (!fd.is_dir(dep_dir)) { + log.console(' installing dependency: ' + dep_locator) + Shop.update(dep_locator) + Shop.fetch(dep_locator) + Shop.extract(dep_locator) + } + push(queue, dep_locator) + } + }) + } + } + // Build scripts for all packages Shop.build_package_scripts(best_pkg) + arrfor(array(all_deps), function(dep) { + Shop.build_package_scripts(dep) + }) + // Build C modules + build_mod = use_cache['core/build'] + if (build_mod) { + _build_c = function() { + target = build_mod.detect_host_target() + arrfor(array(all_deps), function(dep) { + build_mod.build_dynamic(dep, target, 'release') + }) + build_mod.build_dynamic(best_pkg, target, 'release') + } disruption {} + _build_c() + } } disruption { return null } @@ -1600,10 +1658,16 @@ function get_package_zip(pkg) // Update: Check for new version, update lock, fetch and extract // Returns the new lock entry if updated, null if already up to date or failed Shop.update = function(pkg) { + Shop.verify_package_name(pkg) var lock = Shop.load_lock() var lock_entry = lock[pkg] var info = Shop.resolve_package_info(pkg) + if (!info) { + log.error("Not a valid package locator: " + pkg) + return null + } + log.shop(`checking ${pkg}`) var new_entry = null