From 20c2576fa71edae1b24ce07b01564cd1f7ae0400 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Sat, 21 Feb 2026 02:18:42 -0600 Subject: [PATCH] working link --- build.cm | 12 ++++--- internal/shop.cm | 84 +++++++++++++++++++++++++++++++++++------------- link.ce | 13 ++++---- 3 files changed, 76 insertions(+), 33 deletions(-) diff --git a/build.cm b/build.cm index e9fd4ab9..70fcf93b 100644 --- a/build.cm +++ b/build.cm @@ -390,11 +390,13 @@ Build.compile_file = function(pkg, file, target, opts) { // Layer 2: stat-based manifest probe (zero file reads on warm cache) var mf_obj = null + var _linked = fd.is_link(setup.pkg_dir) + var _tag = _linked ? ' [linked]' : '' if (!_opts.force) { mf_obj = bmfst_probe(setup.cmd_str, setup.src_path) if (mf_obj) { - if (_opts.verbose) log.build('[verbose] manifest hit: ' + file) - log.shop('manifest hit ' + file) + if (_opts.verbose) log.build(`[verbose] manifest hit: ${pkg}/${file}${_tag}`) + log.shop(`manifest hit ${pkg}/${file}${_tag}`) return mf_obj } } @@ -578,11 +580,13 @@ Build.build_module_dylib = function(pkg, file, target, opts) { // Stat-based dylib manifest — zero file reads on warm cache var mf_dylib = null + var _linked = fd.is_link(setup.pkg_dir) + var _tag = _linked ? ' [linked]' : '' if (!_opts.force) { mf_dylib = bmfst_dl_probe(setup, link_info) if (mf_dylib) { - if (_opts.verbose) log.build('[verbose] manifest hit: ' + file) - log.shop('manifest hit ' + file) + if (_opts.verbose) log.build(`[verbose] manifest hit: ${pkg}/${file}${_tag}`) + log.shop(`manifest hit ${pkg}/${file}${_tag}`) return mf_dylib } } diff --git a/internal/shop.cm b/internal/shop.cm index c8545949..76b43e2e 100644 --- a/internal/shop.cm +++ b/internal/shop.cm @@ -139,6 +139,42 @@ function package_in_shop(package) { return package in lock } +// Derive canonical lock name from a directory's git origin remote. +// Reads .git/config, extracts the origin url, strips https:// and .git suffix, +// then checks if that name exists in the lock file. +function git_origin_to_lock_name(dir) { + var git_cfg = dir + '/.git/config' + var raw = null + var lines = null + var in_origin = false + var url = null + var candidate = null + if (!fd.is_file(git_cfg)) return null + raw = text(fd.slurp(git_cfg)) + if (!raw) return null + lines = array(raw, '\n') + arrfor(lines, function(line) { + var trimmed = trim(line) + if (trimmed == '[remote "origin"]') { + in_origin = true + } else if (starts_with(trimmed, '[')) { + in_origin = false + } else if (in_origin && starts_with(trimmed, 'url = ')) { + url = trim(text(trimmed, 6)) + } + }) + if (!url) return null + candidate = url + if (starts_with(candidate, 'https://')) + candidate = text(candidate, 8) + else if (starts_with(candidate, 'http://')) + candidate = text(candidate, 7) + if (ends_with(candidate, '.git')) + candidate = text(candidate, 0, length(candidate) - 4) + if (package_in_shop(candidate)) return candidate + return null +} + function abs_path_to_package(package_dir) { if (!fd.is_file(package_dir + '/cell.toml')) { @@ -182,20 +218,21 @@ function abs_path_to_package(package_dir) if (package_in_shop(package_dir)) return package_dir - // For local directories (e.g., linked targets), read the package name from cell.toml - var _toml_path = package_dir + '/cell.toml' - var content = null - var cfg = null - if (fd.is_file(_toml_path)) { - content = text(fd.slurp(_toml_path)) - cfg = toml.decode(content) - if (cfg.package) - return cfg.package - } + // For local directories, try git remote origin to derive canonical name + var _git_name = git_origin_to_lock_name(package_dir) + if (_git_name) return _git_name return package_dir } +function safe_canonicalize(pkg) { + if (!pkg || !starts_with(pkg, '/')) return pkg + var _canon = null + var _try = function() { _canon = abs_path_to_package(pkg) } disruption {} + _try() + return (_canon && _canon != pkg) ? _canon : pkg +} + // given a file, find the absolute path, package name, and import name Shop.file_info = function(file) { var info = { @@ -828,7 +865,7 @@ function resolve_path(path, ctx) if (fd.is_file(ctx_path)) { is_core = (ctx == 'core') || is_core_dir(ctx_dir) scope = is_core ? SCOPE_CORE : SCOPE_LOCAL - return {path: ctx_path, scope: scope, pkg: is_core ? 'core' : ctx} + return {path: ctx_path, scope: scope, pkg: is_core ? 'core' : safe_canonicalize(ctx)} } if (is_internal_path(path)) @@ -907,9 +944,10 @@ function read_dylib_manifest(pkg) { // Ensure all C modules for a package are built and loaded. // Returns the array of {file, symbol, dylib} results, cached per package. function ensure_package_dylibs(pkg) { - if (package_dylibs[pkg] != null) return package_dylibs[pkg] - if (pkg == 'core') { - package_dylibs[pkg] = [] + var _pkg = safe_canonicalize(pkg) + if (package_dylibs[_pkg] != null) return package_dylibs[_pkg] + if (_pkg == 'core') { + package_dylibs[_pkg] = [] return [] } @@ -922,23 +960,23 @@ function ensure_package_dylibs(pkg) { target = detect_host_target() if (!target) return null - c_files = pkg_tools.get_c_files(pkg, target, true) + c_files = pkg_tools.get_c_files(_pkg, target, true) if (!c_files || length(c_files) == 0) { - package_dylibs[pkg] = [] + package_dylibs[_pkg] = [] return [] } - log.shop('ensuring C modules for ' + pkg) - results = build_mod.build_dynamic(pkg, target, 'release', {}) + log.shop('ensuring C modules for ' + _pkg) + results = build_mod.build_dynamic(_pkg, target, 'release', {}) } else { // No build module at runtime — read manifest from cell build - results = read_dylib_manifest(pkg) + results = read_dylib_manifest(_pkg) if (!results) return null - log.shop('loaded manifest for ' + pkg + ' (' + text(length(results)) + ' modules)') + log.shop('loaded manifest for ' + _pkg + ' (' + text(length(results)) + ' modules)') } if (results == null) results = [] - package_dylibs[pkg] = results + package_dylibs[_pkg] = results // Preload all sibling dylibs with RTLD_LAZY|RTLD_GLOBAL arrfor(results, function(r) { @@ -982,7 +1020,7 @@ function try_dylib_symbol(sym, pkg, file_stem) { // Resolve a C symbol by searching: // At each scope: check build-cache dylib first, then internal (static) function resolve_c_symbol(path, _pkg_ctx) { - var package_context = is_core_dir(_pkg_ctx) ? 'core' : _pkg_ctx + var package_context = is_core_dir(_pkg_ctx) ? 'core' : safe_canonicalize(_pkg_ctx) var explicit = split_explicit_package_import(path) var sym = null var loader = null @@ -1298,7 +1336,7 @@ Shop.use = function use(path, _pkg_ctx) { log.error("use() expects a text module path, but received a non-text value") disrupt } - var package_context = is_core_dir(_pkg_ctx) ? 'core' : _pkg_ctx + var package_context = is_core_dir(_pkg_ctx) ? 'core' : safe_canonicalize(_pkg_ctx) // Check for embedded module (static builds) var embed_key = 'embedded:' + path var embedded = null diff --git a/link.ce b/link.ce index 0f766e0e..800e1fac 100644 --- a/link.ce +++ b/link.ce @@ -140,17 +140,18 @@ if (cmd == 'list') { return } - // Read package name from cell.toml + // Derive canonical package name from the target directory _read_toml = function() { - content = toml.decode(text(fd.slurp(toml_path))) - if (content.package) { - pkg_name = content.package + var info = shop.file_info(target + '/cell.toml') + if (info && info.package) { + pkg_name = info.package } else { - log.console("Error: cell.toml at " + target + " does not define 'package'") + log.console("Error: could not determine package name for " + target) + log.console("Ensure it is installed or has a git remote matching a lock entry") $stop() } } disruption { - log.console("Error reading cell.toml") + log.console("Error determining package name for " + target) $stop() } _read_toml()