diff --git a/add.ce b/add.ce index 3749242c..ba2ce584 100644 --- a/add.ce +++ b/add.ce @@ -9,20 +9,18 @@ var shop = use('internal/shop') var pkg = use('package') -var build = use('build') var fd = use('fd') var locator = null var alias = null -var resolved = null -var parts = null -var cwd = null -var build_target = null var recursive = false +var cwd = fd.realpath('.') +var parts = null var locators = null var added = 0 var failed = 0 -var summary = null +var _add_dep = null +var _install = null array(args, function(arg) { if (arg == '--help' || arg == '-h') { @@ -52,89 +50,65 @@ if (!locator && !recursive) { $stop() } -// Resolve relative paths to absolute paths -if (locator && (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator))) { - resolved = fd.realpath(locator) - if (resolved) { - locator = resolved - } -} +if (locator) + locator = shop.resolve_locator(locator) // Generate default alias from locator if (!alias && locator) { - // Use the last component of the locator as alias parts = array(locator, '/') alias = parts[length(parts) - 1] - // Remove any version suffix - if (search(alias, '@') != null) { + if (search(alias, '@') != null) alias = array(alias, '@')[0] - } } // Check we're in a package directory -cwd = fd.realpath('.') if (!fd.is_file(cwd + '/cell.toml')) { log.error("Not in a package directory (no cell.toml found)") $stop() } -// If -r flag, find all packages recursively and add each +// Recursive mode if (recursive) { - if (!locator) { - locator = '.' - } - resolved = fd.realpath(locator) - if (!resolved || !fd.is_dir(resolved)) { + if (!locator) locator = '.' + locator = shop.resolve_locator(locator) + if (!fd.is_dir(locator)) { log.error(`${locator} is not a directory`) $stop() } - locators = filter(pkg.find_packages(resolved), function(p) { + locators = filter(pkg.find_packages(locator), function(p) { return p != cwd }) if (length(locators) == 0) { - log.console("No packages found in " + resolved) + log.console("No packages found in " + locator) $stop() } - log.console(`Found ${text(length(locators))} package(s) in ${resolved}`) + log.console(`Found ${text(length(locators))} package(s) in ${locator}`) + added = 0 + failed = 0 arrfor(locators, function(loc) { - // Generate alias from directory name var loc_parts = array(loc, '/') var loc_alias = loc_parts[length(loc_parts) - 1] - log.console(" Adding " + loc + " as '" + loc_alias + "'...") var _add = function() { pkg.add_dependency(null, loc, loc_alias) - shop.get(loc) - shop.extract(loc) - shop.build_package_scripts(loc) - var _build_c = function() { - build_target = build.detect_host_target() - build.build_dynamic(loc, build_target, 'release') - } disruption { - // Not all packages have C code - } - _build_c() - added++ + shop.sync(loc) + added = added + 1 } disruption { log.console(` Warning: Failed to add ${loc}`) - failed++ + failed = failed + 1 } _add() }) - summary = "Added " + text(added) + " package(s)." - if (failed > 0) { - summary += " Failed: " + text(failed) + "." - } - log.console(summary) + log.console("Added " + text(added) + " package(s)." + (failed > 0 ? " Failed: " + text(failed) + "." : "")) $stop() } +// Single package add log.console("Adding " + locator + " as '" + alias + "'...") -// Add to local project's cell.toml -var _add_dep = function() { +_add_dep = function() { pkg.add_dependency(null, locator, alias) log.console(" Added to cell.toml") } disruption { @@ -143,26 +117,8 @@ var _add_dep = function() { } _add_dep() -// Install to shop -var _install = function() { - shop.get(locator) - shop.extract(locator) - - // Build scripts - var script_result = shop.build_package_scripts(locator) - if (length(script_result.errors) > 0) { - log.console(" Warning: " + text(length(script_result.errors)) + " script(s) failed to compile") - } - - // Build C code if any - var _build_c = function() { - build_target = build.detect_host_target() - build.build_dynamic(locator, build_target, 'release') - } disruption { - // Not all packages have C code - } - _build_c() - +_install = function() { + shop.sync_with_deps(locator) log.console(" Installed to shop") } disruption { log.error("Failed to install") diff --git a/audit.ce b/audit.ce index 8b228098..948f76db 100644 --- a/audit.ce +++ b/audit.ce @@ -10,11 +10,9 @@ var shop = use('internal/shop') var pkg = use('package') -var fd = use('fd') var target_package = null var i = 0 -var resolved = null for (i = 0; i < length(args); i++) { if (args[i] == '--help' || args[i] == '-h') { @@ -30,12 +28,7 @@ for (i = 0; i < length(args); i++) { // Resolve local paths if (target_package) { - if (target_package == '.' || starts_with(target_package, './') || starts_with(target_package, '../') || fd.is_dir(target_package)) { - resolved = fd.realpath(target_package) - if (resolved) { - target_package = resolved - } - } + target_package = shop.resolve_locator(target_package) } var packages = null diff --git a/build.ce b/build.ce index 7d3b445a..fd025aa3 100644 --- a/build.ce +++ b/build.ce @@ -22,7 +22,6 @@ var dry_run = false var i = 0 var targets = null var t = 0 -var resolved = null var lib = null var results = null var success = 0 @@ -74,15 +73,8 @@ for (i = 0; i < length(args); i++) { } } -// Resolve local paths to absolute paths -if (target_package) { - if (target_package == '.' || starts_with(target_package, './') || starts_with(target_package, '../') || fd.is_dir(target_package)) { - resolved = fd.realpath(target_package) - if (resolved) { - target_package = resolved - } - } -} +if (target_package) + target_package = shop.resolve_locator(target_package) // Detect target if not specified if (!target) { @@ -97,10 +89,9 @@ if (target && !build.has_target(target)) { } var packages = shop.list_packages() -log.console('Preparing packages...') arrfor(packages, function(package) { if (package == 'core') return - shop.extract(package) + shop.sync(package, {no_build: true}) }) var _build = null diff --git a/build.cm b/build.cm index e8b5cc88..676f23dd 100644 --- a/build.cm +++ b/build.cm @@ -118,19 +118,7 @@ function get_build_dir() { return shop.get_build_dir() } -function ensure_dir(path) { - if (fd.stat(path).isDirectory) return - var parts = array(path, '/') - var current = starts_with(path, '/') ? '/' : '' - var i = 0 - for (i = 0; i < length(parts); i++) { - if (parts[i] == '') continue - current += parts[i] + '/' - if (!fd.stat(current).isDirectory) fd.mkdir(current) - } -} - -Build.ensure_dir = ensure_dir +Build.ensure_dir = fd.ensure_dir // ============================================================================ // Dependency scanning helpers @@ -267,7 +255,7 @@ Build.compile_file = function(pkg, file, target, opts) { var fail_path = cache_path(quick_content, SALT_FAIL) var build_dir = get_build_dir() - ensure_dir(build_dir) + fd.ensure_dir(build_dir) // Check for cached failure (skip files that previously failed to compile) if (fd.is_file(fail_path)) { @@ -429,7 +417,7 @@ Build.build_module_dylib = function(pkg, file, target, opts) { all_objects = array(all_objects, _extra) var link_content = compute_link_content(all_objects, resolved_ldflags, target_ldflags, {target: _target, cc: cc}) var build_dir = get_build_dir() - ensure_dir(build_dir) + fd.ensure_dir(build_dir) var dylib_path = cache_path(link_content, SALT_DYLIB) var cmd_parts = null var cmd_str = null @@ -703,7 +691,7 @@ Build.compile_native = function(src_path, target, buildtype, pkg) { var src = text(fd.slurp(src_path)) var native_key = native_cache_content(src, _target, san_flags) var build_dir = get_build_dir() - ensure_dir(build_dir) + fd.ensure_dir(build_dir) var dylib_path = cache_path(native_key, SALT_NATIVE) if (fd.is_file(dylib_path)) @@ -775,7 +763,7 @@ Build.compile_native_ir = function(optimized, src_path, opts) { var src = text(fd.slurp(src_path)) var native_key = native_cache_content(src, _target, san_flags) var build_dir = get_build_dir() - ensure_dir(build_dir) + fd.ensure_dir(build_dir) var dylib_path = cache_path(native_key, SALT_NATIVE) if (fd.is_file(dylib_path)) diff --git a/clean.ce b/clean.ce index 89c80bc7..9b5cceed 100644 --- a/clean.ce +++ b/clean.ce @@ -24,7 +24,6 @@ var clean_fetch = false var deep = false var dry_run = false var i = 0 -var resolved = null var deps = null for (i = 0; i < length(args); i++) { @@ -76,12 +75,7 @@ var is_shop_scope = (scope == 'shop') var is_world_scope = (scope == 'world') if (!is_shop_scope && !is_world_scope) { - if (scope == '.' || starts_with(scope, './') || starts_with(scope, '../') || fd.is_dir(scope)) { - resolved = fd.realpath(scope) - if (resolved) { - scope = resolved - } - } + scope = shop.resolve_locator(scope) } var files_to_delete = [] diff --git a/clone.ce b/clone.ce index 9f9b1afe..5343fc31 100644 --- a/clone.ce +++ b/clone.ce @@ -5,11 +5,6 @@ var shop = use('internal/shop') var link = use('link') var fd = use('fd') var http = use('http') -var miniz = use('miniz') - -var resolved = null -var cwd = null -var parent = null if (length(args) < 2) { log.console("Usage: cell clone ") @@ -21,24 +16,7 @@ var origin = args[0] var target_path = args[1] // Resolve target path to absolute -if (target_path == '.' || starts_with(target_path, './') || starts_with(target_path, '../')) { - resolved = fd.realpath(target_path) - if (resolved) { - target_path = resolved - } else { - // Path doesn't exist yet, resolve relative to cwd - cwd = fd.realpath('.') - if (target_path == '.') { - target_path = cwd - } else if (starts_with(target_path, './')) { - target_path = cwd + text(target_path, 1) - } else if (starts_with(target_path, '../')) { - // Go up one directory from cwd - parent = fd.dirname(cwd) - target_path = parent + text(target_path, 2) - } - } -} +target_path = shop.resolve_locator(target_path) // Check if target already exists if (fd.is_dir(target_path)) { @@ -73,48 +51,9 @@ if (!entry || !entry.commit) { var download_url = shop.get_download_url(origin, entry.commit) log.console("Downloading from " + download_url) -var zip_blob = null -var zip = null -var count = 0 -var i = 0 -var filename = null -var first_slash = null -var rel_path = null -var full_path = null -var dir_path = null - var _clone = function() { - zip_blob = http.fetch(download_url) - - // Extract zip to target path - zip = miniz.read(zip_blob) - if (!zip) { - log.console("Error: Failed to read zip archive") - $stop() - } - - // Create target directory - fd.mkdir(target_path) - - count = zip.count() - for (i = 0; i < count; i++) { - if (zip.is_directory(i)) continue - filename = zip.get_filename(i) - first_slash = search(filename, '/') - if (first_slash == null) continue - if (first_slash + 1 >= length(filename)) continue - - rel_path = text(filename, first_slash + 1) - full_path = target_path + '/' + rel_path - dir_path = fd.dirname(full_path) - - // Ensure directory exists - if (!fd.is_dir(dir_path)) { - fd.mkdir(dir_path) - } - fd.slurpwrite(full_path, zip.slurp(filename)) - } - + var zip_blob = http.fetch(download_url) + shop.install_zip(zip_blob, target_path) log.console("Extracted to " + target_path) // Link the origin to the cloned path diff --git a/diff.ce b/diff.ce index 4fe58b2f..f10bf111 100644 --- a/diff.ce +++ b/diff.ce @@ -8,6 +8,7 @@ var shop = use('internal/shop') var pkg = use('package') var fd = use('fd') var time = use('time') +var testlib = use('internal/testlib') var _args = args == null ? [] : args @@ -27,10 +28,7 @@ if (length(_args) > 0) { target_test = _args[0] } -function is_valid_package(dir) { - var _dir = dir == null ? '.' : dir - return fd.is_file(_dir + '/cell.toml') -} +var is_valid_package = testlib.is_valid_package if (!is_valid_package('.')) { log.console('No cell.toml found in current directory') @@ -63,47 +61,8 @@ function collect_tests(specific_test) { return test_files } -// Deep comparison of two values -function values_equal(a, b) { - var i = 0 - var ka = null - var kb = null - if (a == b) return true - if (is_null(a) && is_null(b)) return true - if (is_null(a) || is_null(b)) return false - if (is_array(a) && is_array(b)) { - if (length(a) != length(b)) return false - i = 0 - while (i < length(a)) { - if (!values_equal(a[i], b[i])) return false - i = i + 1 - } - return true - } - if (is_object(a) && is_object(b)) { - ka = array(a) - kb = array(b) - if (length(ka) != length(kb)) return false - i = 0 - while (i < length(ka)) { - if (!values_equal(a[ka[i]], b[ka[i]])) return false - i = i + 1 - } - return true - } - return false -} - -function describe(val) { - if (is_null(val)) return "null" - if (is_text(val)) return `"${val}"` - if (is_number(val)) return text(val) - if (is_logical(val)) return text(val) - if (is_function(val)) return "" - if (is_array(val)) return `[array length=${text(length(val))}]` - if (is_object(val)) return `{record keys=${text(length(array(val)))}}` - return "" -} +var values_equal = testlib.values_equal +var describe = testlib.describe // Run a single test file through both paths function diff_test_file(file_path) { diff --git a/fd.cm b/fd.cm index ea5fbfd6..dc9c056a 100644 --- a/fd.cm +++ b/fd.cm @@ -97,4 +97,24 @@ fd.globfs = function(globs, dir) { return results } +fd.ensure_dir = function ensure_dir(path) { + if (fd.is_dir(path)) return true + var parts = array(path, '/') + var current = starts_with(path, '/') ? '/' : '' + var i = 0 + for (i = 0; i < length(parts); i++) { + if (parts[i] == '') continue + current = current + parts[i] + '/' + if (!fd.is_dir(current)) + fd.mkdir(current) + } + return true +} + +fd.safe_package_path = function safe_package_path(pkg) { + if (pkg && starts_with(pkg, '/')) + return replace(replace(pkg, '/', '_'), '@', '_') + return replace(pkg, '@', '_') +} + return fd diff --git a/fetch.ce b/fetch.ce index cc526740..f2b5c45a 100644 --- a/fetch.ce +++ b/fetch.ce @@ -1,104 +1,46 @@ -// cell fetch - Fetch package zips from remote sources +// cell fetch - Sync packages from remote sources // -// This command ensures that the zip files on disk match what's in the lock file. -// For local packages, this is a no-op. -// For remote packages, downloads the zip if not present or hash mismatch. +// Ensures all packages are fetched, extracted, compiled, and ready to use. +// For local packages, this is a no-op (symlinks only). // // Usage: -// cell fetch - Fetch all packages -// cell fetch - Fetch a specific package +// cell fetch - Sync all packages +// cell fetch - Sync a specific package var shop = use('internal/shop') -// Parse arguments var target_pkg = null var i = 0 +var packages = null +var count = 0 for (i = 0; i < length(args); i++) { if (args[i] == '--help' || args[i] == '-h') { log.console("Usage: cell fetch [package]") - log.console("Fetch package zips from remote sources.") + log.console("Sync packages from remote sources.") log.console("") log.console("Arguments:") - log.console(" package Optional package name to fetch. If omitted, fetches all.") - log.console("") - log.console("This command ensures that the zip files on disk match what's in") - log.console("the lock file. For local packages, this is a no-op.") + log.console(" package Optional package to sync. If omitted, syncs all.") $stop() } else if (!starts_with(args[i], '-')) { target_pkg = args[i] } } -var all_packages = shop.list_packages() -var lock = shop.load_lock() -var packages_to_fetch = [] -var _update = null - if (target_pkg) { - // Fetch specific package - auto-update if not in lock - if (find(all_packages, target_pkg) == null) { - log.console("Package not in lock, updating: " + target_pkg) - _update = function() { - shop.update(target_pkg) - } disruption { - log.error("Could not update package: " + target_pkg) - $stop() - } - _update() - // Reload after update - all_packages = shop.list_packages() - lock = shop.load_lock() - if (find(all_packages, target_pkg) == null) { - log.error("Package not found: " + target_pkg) - $stop() - } - } - push(packages_to_fetch, target_pkg) + target_pkg = shop.resolve_locator(target_pkg) + log.console("Syncing " + target_pkg + "...") + shop.sync(target_pkg) + log.console("Done.") } else { - // Fetch all packages - packages_to_fetch = all_packages + packages = shop.list_packages() + count = 0 + arrfor(packages, function(pkg) { + if (pkg == 'core') return + shop.sync(pkg) + count = count + 1 + }) + log.console("Synced " + text(count) + " package(s).") } -var remote_count = 0 -arrfor(packages_to_fetch, function(pkg) { - var entry = lock[pkg] - if (pkg != 'core' && (!entry || entry.type != 'local')) - remote_count++ -}, null, null) - -if (remote_count > 0) - log.console(`Fetching ${text(remote_count)} remote package(s)...`) - -var downloaded_count = 0 -var cached_count = 0 -var fail_count = 0 - -arrfor(packages_to_fetch, function(pkg) { - // Skip core (handled separately) - if (pkg == 'core') return - - var result = shop.fetch(pkg) - if (result.status == 'local') { - // Local packages are just symlinks, nothing to fetch - return - } else if (result.status == 'cached') { - cached_count++ - } else if (result.status == 'downloaded') { - log.console(" Downloaded: " + pkg) - downloaded_count++ - } else if (result.status == 'error') { - log.error(" Failed: " + pkg + (result.message ? " - " + result.message : "")) - fail_count++ - } -}, null, null) - -log.console("") -var parts = [] -if (downloaded_count > 0) push(parts, `${text(downloaded_count)} downloaded`) -if (cached_count > 0) push(parts, `${text(cached_count)} cached`) -if (fail_count > 0) push(parts, `${text(fail_count)} failed`) -if (length(parts) == 0) push(parts, "nothing to fetch") -log.console("Fetch complete: " + text(parts, ", ")) - $stop() diff --git a/fuzz.ce b/fuzz.ce index 832410a2..7b5c68c5 100644 --- a/fuzz.ce +++ b/fuzz.ce @@ -12,6 +12,7 @@ var fd = use('fd') var time = use('time') var json = use('json') +var testlib = use('internal/testlib') var os_ref = use('internal/os') var analyze = os_ref.analyze @@ -54,48 +55,9 @@ if (!run_ast_noopt_fn) { // Ensure failures directory exists var failures_dir = "tests/fuzz_failures" -function ensure_dir(path) { - if (fd.is_dir(path)) return - var parts = array(path, '/') - var current = '' - var j = 0 - while (j < length(parts)) { - if (parts[j] != '') { - current = current + parts[j] + '/' - if (!fd.is_dir(current)) { - fd.mkdir(current) - } - } - j = j + 1 - } -} - -// Deep comparison -function values_equal(a, b) { - var j = 0 - if (a == b) return true - if (is_null(a) && is_null(b)) return true - if (is_null(a) || is_null(b)) return false - if (is_array(a) && is_array(b)) { - if (length(a) != length(b)) return false - j = 0 - while (j < length(a)) { - if (!values_equal(a[j], b[j])) return false - j = j + 1 - } - return true - } - return false -} - -function describe(val) { - if (is_null(val)) return "null" - if (is_text(val)) return `"${val}"` - if (is_number(val)) return text(val) - if (is_logical(val)) return text(val) - if (is_function(val)) return "" - return "" -} +var ensure_dir = fd.ensure_dir +var values_equal = testlib.values_equal +var describe = testlib.describe // Run a single fuzz iteration function run_fuzz(seed_val) { diff --git a/graph.ce b/graph.ce index 07d24566..e8eb2cea 100644 --- a/graph.ce +++ b/graph.ce @@ -15,7 +15,6 @@ var shop = use('internal/shop') var pkg = use('package') var link = use('link') -var fd = use('fd') var json = use('json') var target_locator = null @@ -23,7 +22,6 @@ var format = 'tree' var show_locked = false var show_world = false var i = 0 -var resolved = null for (i = 0; i < length(args); i++) { if (args[i] == '--format' || args[i] == '-f') { @@ -127,13 +125,7 @@ if (show_world) { target_locator = '.' } - // Resolve local paths - if (target_locator == '.' || starts_with(target_locator, './') || starts_with(target_locator, '../') || fd.is_dir(target_locator)) { - resolved = fd.realpath(target_locator) - if (resolved) { - target_locator = resolved - } - } + target_locator = shop.resolve_locator(target_locator) push(roots, target_locator) } diff --git a/install.ce b/install.ce index c5f2c818..41deb8df 100644 --- a/install.ce +++ b/install.ce @@ -1,4 +1,4 @@ -// cell install - Install a package to the shop +// cell install - Install a package and its dependencies // // Usage: // cell install Install a package and its dependencies @@ -6,34 +6,23 @@ // // Options: // --target Build for target platform -// --refresh Refresh floating refs before locking // --dry-run Show what would be installed // -r Recursively find and install all packages in directory var shop = use('internal/shop') -var build = use('build') var pkg = use('package') var fd = use('fd') -if (length(args) < 1) { - log.console("Usage: cell install [options]") - log.console("") - log.console("Options:") - log.console(" --target Build for target platform") - log.console(" --refresh Refresh floating refs before locking") - log.console(" --dry-run Show what would be installed") - log.console(" -r Recursively find and install all packages in directory") - $stop() -} - var locator = null var target_triple = null -var refresh = false var dry_run = false var recursive = false var i = 0 -var resolved = null var locators = null +var cwd = fd.realpath('.') +var lock = null +var installed = 0 +var failed = 0 for (i = 0; i < length(args); i++) { if (args[i] == '--target' || args[i] == '-t') { @@ -43,8 +32,6 @@ for (i = 0; i < length(args); i++) { log.error('--target requires a triple') $stop() } - } else if (args[i] == '--refresh') { - refresh = true } else if (args[i] == '--dry-run') { dry_run = true } else if (args[i] == '-r') { @@ -52,11 +39,10 @@ for (i = 0; i < length(args); i++) { } else if (args[i] == '--help' || args[i] == '-h') { log.console("Usage: cell install [options]") log.console("") - log.console("Install a package and its dependencies to the shop.") + log.console("Install a package and its dependencies.") log.console("") log.console("Options:") log.console(" --target Build for target platform") - log.console(" --refresh Refresh floating refs before locking") log.console(" --dry-run Show what would be installed") log.console(" -r Recursively find and install all packages in directory") $stop() @@ -66,197 +52,65 @@ for (i = 0; i < length(args); i++) { } if (!locator && !recursive) { - log.console("Usage: cell install ") + log.console("Usage: cell install [options]") $stop() } -// Resolve relative paths to absolute paths -// Local paths like '.' or '../foo' need to be converted to absolute paths -if (locator && (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator))) { - resolved = fd.realpath(locator) - if (resolved) { - locator = resolved - } -} +if (locator) + locator = shop.resolve_locator(locator) -var cwd = fd.realpath('.') - -// If -r flag, find all packages recursively and install each +// Recursive mode: find all packages in directory and install each if (recursive) { - if (!locator) { - locator = '.' - } - resolved = fd.realpath(locator) - if (!resolved || !fd.is_dir(resolved)) { + if (!locator) locator = '.' + locator = shop.resolve_locator(locator) + if (!fd.is_dir(locator)) { log.error(`${locator} is not a directory`) $stop() } - locators = filter(pkg.find_packages(resolved), function(p) { + locators = filter(pkg.find_packages(locator), function(p) { return p != cwd }) if (length(locators) == 0) { - log.console("No packages found in " + resolved) + log.console("No packages found in " + locator) $stop() } - log.console(`Found ${text(length(locators))} package(s) in ${resolved}`) -} + log.console(`Found ${text(length(locators))} package(s) in ${locator}`) -// Default target -if (!target_triple) { - target_triple = build.detect_host_target() -} - -// Gather all packages that will be installed -var packages_to_install = [] -var skipped_packages = [] -var summary = null -var visited = {} - -// Recursive mode: install all found packages and exit -if (recursive) { if (dry_run) { log.console("Would install:") arrfor(locators, function(loc) { - var lock = shop.load_lock() - var exists = lock[loc] != null - log.console(" " + loc + (exists ? " (already installed)" : "")) + lock = shop.load_lock() + log.console(" " + loc + (lock[loc] ? " (already installed)" : "")) }) $stop() } + installed = 0 + failed = 0 arrfor(locators, function(loc) { log.console(" Installing " + loc + "...") var _inst = function() { - shop.update(loc) - shop.extract(loc) - shop.build_package_scripts(loc) - var _build_c = function() { - build.build_dynamic(loc, target_triple, 'release') - } disruption { - // Not all packages have C code - } - _build_c() - push(packages_to_install, loc) + shop.sync(loc, {target: target_triple}) + installed = installed + 1 } disruption { - push(skipped_packages, loc) + failed = failed + 1 log.console(` Warning: Failed to install ${loc}`) } _inst() }) - summary = "Installed " + text(length(packages_to_install)) + " package(s)." - if (length(skipped_packages) > 0) { - summary += " Failed: " + text(length(skipped_packages)) + "." - } - log.console(summary) + log.console("Installed " + text(installed) + " package(s)." + (failed > 0 ? " Failed: " + text(failed) + "." : "")) + $stop() +} + +// Single package install with dependencies +if (dry_run) { + log.console("Would install: " + locator + " (and dependencies)") $stop() } log.console("Installing " + locator + "...") - -function gather_packages(pkg_locator) { - var lock = null - var update_result = null - var deps = null - if (visited[pkg_locator]) return - visited[pkg_locator] = true - - // Check if this is a local path that doesn't exist - if (starts_with(pkg_locator, '/') && !fd.is_dir(pkg_locator)) { - push(skipped_packages, pkg_locator) - log.console(" Skipping missing local package: " + pkg_locator) - return - } - - push(packages_to_install, pkg_locator) - - // Try to read dependencies - var _gather = function() { - // For packages not yet extracted, we need to update and extract first to read deps - lock = shop.load_lock() - if (!lock[pkg_locator]) { - if (!dry_run) { - update_result = shop.update(pkg_locator) - if (update_result) { - shop.extract(pkg_locator) - } else { - // Update failed - package might not be fetchable - log.console("Warning: Could not fetch " + pkg_locator) - return - } - } - } else { - // Package is in lock, ensure it's extracted - if (!dry_run) { - shop.extract(pkg_locator) - } - } - - deps = pkg.dependencies(pkg_locator) - if (deps) { - arrfor(array(deps), function(alias) { - var dep_locator = deps[alias] - gather_packages(dep_locator) - }) - } - } disruption { - // Package might not have dependencies or cell.toml issue - if (!dry_run) { - log.console(`Warning: Could not read dependencies for ${pkg_locator}`) - } - } - _gather() -} - -// Gather all packages -gather_packages(locator) - -if (dry_run) { - log.console("Would install:") - arrfor(packages_to_install, function(p) { - var lock = shop.load_lock() - var exists = lock[p] != null - log.console(" " + p + (exists ? " (already installed)" : "")) - }) - if (length(skipped_packages) > 0) { - log.console("") - log.console("Would skip (missing local paths):") - arrfor(skipped_packages, function(p) { - log.console(" " + p) - }) - } - $stop() -} - -// Install each package -function install_package(pkg_locator) { - // Update lock entry - shop.update(pkg_locator) - - // Extract/symlink the package - shop.extract(pkg_locator) - - // Build scripts - shop.build_package_scripts(pkg_locator) - - // Build C code - var _build_c = function() { - build.build_dynamic(pkg_locator, target_triple, 'release') - } disruption { - // Not all packages have C code - } - _build_c() -} - -arrfor(packages_to_install, function(p) { - log.console(" Installing " + p + "...") - install_package(p) -}) - -summary = "Installed " + text(length(packages_to_install)) + " package(s)." -if (length(skipped_packages) > 0) { - summary += " Skipped " + text(length(skipped_packages)) + " missing local path(s)." -} -log.console(summary) +shop.sync_with_deps(locator, {refresh: true, target: target_triple}) +log.console("Done.") $stop() diff --git a/internal/engine.cm b/internal/engine.cm index e86c8c54..154e13e0 100644 --- a/internal/engine.cm +++ b/internal/engine.cm @@ -1466,20 +1466,12 @@ $_.clock(_ => { if (!fd.is_dir(_dep_dir)) { log.console('installing missing dependency: ' + _deps[_di]) _auto_install = function() { - shop.update(_deps[_di]) - shop.fetch(_deps[_di]) - shop.extract(_deps[_di]) - shop.build_package_scripts(_deps[_di]) + shop.sync(_deps[_di]) } disruption { log.error('failed to install dependency: ' + _deps[_di]) disrupt } _auto_install() - _dep_dir = package.get_dir(_deps[_di]) - if (!fd.is_dir(_dep_dir)) { - log.error('missing dependency package: ' + _deps[_di]) - disrupt - } } _di = _di + 1 } diff --git a/internal/shop.cm b/internal/shop.cm index 9271b3d2..15265a7d 100644 --- a/internal/shop.cm +++ b/internal/shop.cm @@ -40,20 +40,6 @@ function put_into_cache(content, obj) fd.slurpwrite(path, obj) } -function ensure_dir(path) { - if (fd.stat(path).isDirectory) return - var parts = array(path, '/') - var current = starts_with(path, '/') ? '/' : '' - var i = 0 - for (i = 0; i < length(parts); i++) { - if (parts[i] == '') continue - current = current + parts[i] + '/' - if (!fd.stat(current).isDirectory) { - fd.mkdir(current) - } - } -} - function hash_path(content, salt) { var s = salt || 'mach' @@ -88,11 +74,6 @@ Shop.get_core_dir = function() { return get_packages_dir() + '/' + core_package } -// 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' @@ -131,7 +112,7 @@ function split_explicit_package_import(path) mod_path = text(array(parts, i), '/') if (!mod_path || length(mod_path) == 0) continue - candidate_dir = get_packages_dir() + '/' + safe_package_path(pkg_candidate) + candidate_dir = get_packages_dir() + '/' + fd.safe_package_path(pkg_candidate) if (fd.is_file(candidate_dir + '/cell.toml')) return {package: pkg_candidate, path: mod_path} @@ -259,15 +240,15 @@ function get_canonical_package(alias, package_context) { return null } -// return the safe path for the package -// guaranteed to be validated -function safe_package_path(pkg) -{ - // For absolute paths, replace / with _ to create a valid directory name - // Also replace @ with _ - if (pkg && starts_with(pkg, '/')) - return replace(replace(pkg, '/', '_'), '@', '_') - return replace(pkg, '@', '_') +// Resolve a locator string to its canonical form +// Handles '.', './', '../', and existing directory paths +Shop.resolve_locator = function(locator) { + var resolved = null + if (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator)) { + resolved = fd.realpath(locator) + if (resolved) return resolved + } + return locator } function package_cache_path(pkg) @@ -297,7 +278,8 @@ Shop.load_lock = function() { // Save lock.toml configuration (to global shop) Shop.save_lock = function(lock) { var path = global_shop_path + '/lock.toml' - fd.slurpwrite(path, stone(blob(toml.encode(lock)))); + fd.slurpwrite(path, stone(blob(toml.encode(lock)))) + _lock = lock } @@ -447,7 +429,7 @@ function try_native_mod_dylib(pkg, stem) { var build_mod = use_cache['core/build'] if (!build_mod) return null - var src_path = get_packages_dir() + '/' + safe_package_path(pkg) + '/' + stem + var src_path = get_packages_dir() + '/' + fd.safe_package_path(pkg) + '/' + stem if (!fd.is_file(src_path)) return null var src = text(fd.slurp(src_path)) @@ -665,7 +647,7 @@ Shop.all_script_paths = function() { packages = array(packages, ['core']) } for (i = 0; i < length(packages); i++) { - pkg_dir = starts_with(packages[i], '/') ? packages[i] : get_packages_dir() + '/' + safe_package_path(packages[i]) + pkg_dir = starts_with(packages[i], '/') ? packages[i] : get_packages_dir() + '/' + fd.safe_package_path(packages[i]) scripts = get_package_scripts(packages[i]) for (j = 0; j < length(scripts); j++) { result[] = { @@ -710,7 +692,7 @@ function resolve_mod_fn(path, pkg) { // Compute _pkg_dir and _stem early so all paths can use them if (pkg) { - _pkg_dir = get_packages_dir() + '/' + safe_package_path(pkg) + _pkg_dir = get_packages_dir() + '/' + fd.safe_package_path(pkg) if (starts_with(path, _pkg_dir + '/')) { _stem = text(path, length(_pkg_dir) + 1) } @@ -772,7 +754,7 @@ function resolve_mod_fn(path, pkg) { mcode_json = shop_json.encode(optimized) // Cache mcode (architecture-independent) in content-addressed store - ensure_dir(global_shop_path + '/build') + fd.ensure_dir(global_shop_path + '/build') fd.slurpwrite(cached_mcode_path, stone(blob(mcode_json))) // Cache mach blob @@ -813,7 +795,7 @@ function resolve_path(path, ctx) explicit = null } if (explicit) { - explicit_path = get_packages_dir() + '/' + safe_package_path(explicit.package) + '/' + explicit.path + explicit_path = get_packages_dir() + '/' + fd.safe_package_path(explicit.package) + '/' + explicit.path if (fd.is_file(explicit_path)) return {path: explicit_path, scope: SCOPE_PACKAGE, pkg: explicit.package} } @@ -829,7 +811,7 @@ function resolve_path(path, ctx) if (starts_with(ctx, '/')) ctx_dir = ctx else - ctx_dir = get_packages_dir() + '/' + safe_package_path(ctx) + ctx_dir = get_packages_dir() + '/' + fd.safe_package_path(ctx) ctx_path = ctx_dir + '/' + path if (fd.is_file(ctx_path)) { @@ -843,12 +825,12 @@ function resolve_path(path, ctx) alias = pkg_tools.split_alias(ctx, path) if (alias) { - alias_path = get_packages_dir() + '/' + safe_package_path(alias.package) + '/' + alias.path + alias_path = get_packages_dir() + '/' + fd.safe_package_path(alias.package) + '/' + alias.path if (fd.is_file(alias_path)) return {path: alias_path, scope: SCOPE_PACKAGE, pkg: ctx} } - package_path = get_packages_dir() + '/' + safe_package_path(path) + package_path = get_packages_dir() + '/' + fd.safe_package_path(path) if (fd.is_file(package_path)) return {path: package_path, scope: SCOPE_PACKAGE, pkg: ctx} @@ -865,7 +847,7 @@ function resolve_path(path, ctx) } }) if (best_pkg && best_remainder) { - shop_dir = get_packages_dir() + '/' + safe_package_path(best_pkg) + shop_dir = get_packages_dir() + '/' + fd.safe_package_path(best_pkg) shop_file = shop_dir + '/' + best_remainder if (fd.is_file(shop_file)) return {path: shop_file, scope: SCOPE_PACKAGE, pkg: best_pkg} @@ -1283,7 +1265,7 @@ function get_module(path, package_context) { if (!info) { log.shop(`Module '${path}' could not be found in package '${package_context}'`) - _ctx_dir = package_context ? (starts_with(package_context, '/') ? package_context : get_packages_dir() + '/' + safe_package_path(package_context)) : null + _ctx_dir = package_context ? (starts_with(package_context, '/') ? package_context : get_packages_dir() + '/' + fd.safe_package_path(package_context)) : null if (_ctx_dir) { if (fd.is_file(_ctx_dir + '/' + path + '.c') || fd.is_file(_ctx_dir + '/' + path + '.cpp')) log.shop(`C source exists at ${_ctx_dir}/${path}.c but was not compiled - run 'cell build'`) @@ -1319,7 +1301,7 @@ Shop.use = function use(path, package_context) { var _alias2 = null if (!info) { log.shop(`Module '${path}' could not be found in package '${package_context}'`) - _ctx_dir2 = package_context ? (starts_with(package_context, '/') ? package_context : get_packages_dir() + '/' + safe_package_path(package_context)) : null + _ctx_dir2 = package_context ? (starts_with(package_context, '/') ? package_context : get_packages_dir() + '/' + fd.safe_package_path(package_context)) : null if (_ctx_dir2) { if (fd.is_file(_ctx_dir2 + '/' + path + '.c') || fd.is_file(_ctx_dir2 + '/' + path + '.cpp')) log.shop(`C source exists at ${_ctx_dir2}/${path}.c but was not compiled - run 'cell build'`) @@ -1336,8 +1318,6 @@ Shop.use = function use(path, package_context) { return use_cache[info.cache_key] } -Shop.resolve_locator = resolve_locator - // Resolve a use() module path to a filesystem path without compiling. // Returns the absolute path string, or null if not found. Shop.resolve_use_path = function(path, ctx) { @@ -1354,14 +1334,13 @@ Shop.resolve_program = function(prog, package_context) { var info = resolve_path(prog + '.ce', package_context) if (info) return info - // Auto-install: if the path matches a recognized remote locator, try fetching + // Find best matching package from lock or infer from path var lock = Shop.load_lock() var best_pkg = null var best_remainder = null - var pkg_info = null var parts = array(prog, '/') - var i = 0 var candidate = null + var pkg_info = null var _auto = null arrfor(array(lock), function(pkg_name) { if (starts_with(prog, pkg_name + '/')) { @@ -1372,8 +1351,7 @@ Shop.resolve_program = function(prog, package_context) { } }) - // If not in lock, check if this looks like a fetchable package - // For gitea-style URLs, the package root is host/owner/repo (3 components) + // If not in lock, try gitea-style 3-component package (host/owner/repo) if (!best_pkg && length(parts) > 3) { candidate = text(array(parts, 0, 3), '/') pkg_info = Shop.resolve_package_info(candidate) @@ -1383,78 +1361,19 @@ Shop.resolve_program = function(prog, package_context) { } } - 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 - } - _auto() - // Retry resolution - info = resolve_path(prog + '.ce', package_context) - if (info) return info - } + if (!best_pkg || !best_remainder) return null - return null + // Auto-install the package and all its dependencies + log.console('fetching ' + best_pkg + '...') + _auto = function() { + Shop.sync_with_deps(best_pkg) + } disruption { + return null + } + _auto() + + info = resolve_path(prog + '.ce', package_context) + return info } // Resolve a use() module path to {resolved_path, package, type} without compiling. @@ -1486,7 +1405,7 @@ function get_cache_path(pkg, commit) { function get_package_abs_dir(package) { - return get_packages_dir() + '/' + safe_package_path(package) + return get_packages_dir() + '/' + fd.safe_package_path(package) } // Fetch the latest commit hash from remote for a package @@ -1510,7 +1429,7 @@ function fetch_remote_hash(pkg) { // Returns the zip blob or null on failure function download_zip(pkg, commit_hash) { var cache_path = get_cache_path(pkg, commit_hash) - ensure_dir(global_shop_path + '/cache') + fd.ensure_dir(global_shop_path + '/cache') var download_url = Shop.get_download_url(pkg, commit_hash) if (!download_url) { @@ -1732,6 +1651,82 @@ Shop.update = function(pkg) { return new_entry } +// Sync a package: ensure it's in lock, fetched, extracted, and compiled +// opts.refresh - check remote for updates even if lock entry exists +// opts.no_build - skip C module build step +// opts.target - explicit build target (auto-detected if not provided) +// opts.buildtype - 'release'|'debug'|'minsize' (default 'release') +Shop.sync = function(pkg, opts) { + var lock = Shop.load_lock() + var info = Shop.resolve_package_info(pkg) + var build_mod = null + var target = null + var _build_c = null + + // Step 1: Ensure lock entry (update if refresh or not in lock) + if ((opts && opts.refresh) || !lock[pkg]) + Shop.update(pkg) + + // Step 2: Fetch zip (no-op for local packages) + if (info && info != 'local') + Shop.fetch(pkg) + + // Step 3: Extract to packages dir + Shop.extract(pkg) + + // Step 4: Compile scripts + Shop.build_package_scripts(pkg) + + // Step 5: Build C modules + if (!opts || !opts.no_build) { + build_mod = use_cache['core/build'] + if (build_mod) { + target = (opts && opts.target) ? opts.target : build_mod.detect_host_target() + _build_c = function() { + build_mod.build_dynamic(pkg, target, (opts && opts.buildtype) ? opts.buildtype : 'release') + } disruption { + // Not all packages have C code + } + _build_c() + } + } +} + +// Sync a package and all its dependencies (BFS) +Shop.sync_with_deps = function(pkg, opts) { + var visited = {} + var queue = [pkg] + var qi = 0 + var current = null + var deps = null + var dep_locator = null + var _read_deps = null + + while (qi < length(queue)) { + current = queue[qi] + qi = qi + 1 + if (visited[current]) continue + visited[current] = true + + Shop.sync(current, opts) + + _read_deps = function() { + deps = pkg_tools.dependencies(current) + } disruption { + deps = null + } + _read_deps() + + if (deps) { + arrfor(array(deps), function(alias) { + dep_locator = deps[alias] + if (!visited[dep_locator]) + push(queue, dep_locator) + }) + } + } +} + function install_zip(zip_blob, target_dir) { var zip = miniz.read(zip_blob) if (!zip) { print("Failed to read zip archive"); disrupt } @@ -1740,7 +1735,7 @@ function install_zip(zip_blob, target_dir) { if (fd.is_dir(target_dir)) fd.rmdir(target_dir, 1) log.shop("Extracting to " + target_dir) - ensure_dir(target_dir) + fd.ensure_dir(target_dir) var count = zip.count() var created_dirs = {} @@ -1763,7 +1758,7 @@ function install_zip(zip_blob, target_dir) { dir_path = fd.dirname(full_path) if (!created_dirs[dir_path]) { - ensure_dir(dir_path) + fd.ensure_dir(dir_path) created_dirs[dir_path] = true } file_data = zip.slurp(filename) @@ -1784,7 +1779,7 @@ Shop.remove = function(pkg) { } // Remove package symlink/directory - var pkg_dir = get_packages_dir() + '/' + safe_package_path(pkg) + var pkg_dir = get_packages_dir() + '/' + fd.safe_package_path(pkg) if (fd.is_link(pkg_dir)) { fd.unlink(pkg_dir) } else if (fd.is_dir(pkg_dir)) { @@ -1798,34 +1793,6 @@ Shop.remove = function(pkg) { return true } -Shop.get = function(pkg) { - var lock = Shop.load_lock() - var info = null - var commit = null - - if (!lock[pkg]) { - info = Shop.resolve_package_info(pkg) - if (!info) { - print("Invalid package: " + pkg); disrupt - } - - commit = null - if (info != 'local') { - commit = fetch_remote_hash(pkg) - if (!commit) { - print("Could not resolve commit for " + pkg); disrupt - } - } - - lock[pkg] = { - type: info, - commit: commit, - updated: time.number() - } - Shop.save_lock(lock) - } -} - // Compile a module // List all files in a package @@ -1902,6 +1869,7 @@ Shop.build_package_scripts = function(package) ok = ok + 1 } disruption { push(errors, script) + log.console(" compile error: " + package + '/' + script) } _try() }) @@ -1922,7 +1890,8 @@ Shop.get_lib_dir = function() { return global_shop_path + '/lib' } -Shop.ensure_dir = ensure_dir +Shop.ensure_dir = fd.ensure_dir +Shop.install_zip = install_zip Shop.ensure_package_dylibs = ensure_package_dylibs Shop.get_local_dir = function() { @@ -1936,7 +1905,7 @@ Shop.get_build_dir = function() { // Get the absolute path for a package Shop.get_package_dir = function(pkg) { - return get_packages_dir() + '/' + safe_package_path(pkg) + return get_packages_dir() + '/' + fd.safe_package_path(pkg) } // Generate C symbol name for a file within a package @@ -1957,7 +1926,7 @@ Shop.c_symbol_prefix = function(pkg) { // Get the library directory name for a package Shop.lib_name_for_package = function(pkg) { - return safe_package_path(pkg) + return fd.safe_package_path(pkg) } // Load a module explicitly as mach bytecode, bypassing dylib resolution. @@ -2011,7 +1980,7 @@ Shop.load_as_mach = function(path, pkg) { optimized = _streamline_mod(ir) mcode_json = shop_json.encode(optimized) cached_mcode_path = hash_path(content_key, 'mcode') - ensure_dir(global_shop_path + '/build') + fd.ensure_dir(global_shop_path + '/build') fd.slurpwrite(cached_mcode_path, stone(blob(mcode_json))) compiled = mach_compile_mcode_bin(file_path, mcode_json) put_into_cache(content_key, compiled) @@ -2048,7 +2017,7 @@ Shop.load_as_dylib = function(path, pkg) { } if (!real_pkg) return null - pkg_dir = get_packages_dir() + '/' + safe_package_path(real_pkg) + pkg_dir = get_packages_dir() + '/' + fd.safe_package_path(real_pkg) if (!starts_with(file_path, pkg_dir + '/')) return null stem = text(file_path, length(pkg_dir) + 1) result = try_native_mod_dylib(real_pkg, stem) diff --git a/internal/testlib.cm b/internal/testlib.cm index 743db329..03d1709e 100644 --- a/internal/testlib.cm +++ b/internal/testlib.cm @@ -33,26 +33,54 @@ function get_pkg_dir(package_name) { return shop.get_package_dir(package_name) } -// Ensure directory exists -function ensure_dir(path) { - if (fd.is_dir(path)) return true - - var parts = array(path, '/') - var current = starts_with(path, '/') ? '/' : '' +// Deep comparison of two values (handles arrays and objects) +function values_equal(a, b) { var i = 0 - for (i = 0; i < length(parts); i++) { - if (parts[i] == '') continue - current = current + parts[i] + '/' - if (!fd.is_dir(current)) { - fd.mkdir(current) + var ka = null + var kb = null + if (a == b) return true + if (is_null(a) && is_null(b)) return true + if (is_null(a) || is_null(b)) return false + if (is_array(a) && is_array(b)) { + if (length(a) != length(b)) return false + i = 0 + while (i < length(a)) { + if (!values_equal(a[i], b[i])) return false + i = i + 1 } + return true } - return true + if (is_object(a) && is_object(b)) { + ka = array(a) + kb = array(b) + if (length(ka) != length(kb)) return false + i = 0 + while (i < length(ka)) { + if (!values_equal(a[ka[i]], b[ka[i]])) return false + i = i + 1 + } + return true + } + return false +} + +// Describe a value for error messages +function describe(val) { + if (is_null(val)) return "null" + if (is_text(val)) return `"${val}"` + if (is_number(val)) return text(val) + if (is_logical(val)) return text(val) + if (is_function(val)) return "" + if (is_array(val)) return `[array length=${text(length(val))}]` + if (is_object(val)) return `{record keys=${text(length(array(val)))}}` + return "" } return { is_valid_package: is_valid_package, get_current_package_name: get_current_package_name, get_pkg_dir: get_pkg_dir, - ensure_dir: ensure_dir + ensure_dir: fd.ensure_dir, + values_equal: values_equal, + describe: describe } diff --git a/link.ce b/link.ce index 2c552a5e..234c0c65 100644 --- a/link.ce +++ b/link.ce @@ -28,7 +28,6 @@ var target = null var start_idx = 0 var arg1 = null var arg2 = null -var cwd = null var toml_path = null var content = null var _restore = null @@ -123,30 +122,14 @@ if (cmd == 'list') { target = arg2 // Resolve target if it's a local path - if (target == '.' || fd.is_dir(target)) { - target = fd.realpath(target) - } else if (starts_with(target, './') || starts_with(target, '../')) { - // Relative path that doesn't exist yet - try to resolve anyway - cwd = fd.realpath('.') - if (starts_with(target, './')) { - target = cwd + text(target, 1) - } else { - // For ../ paths, let fd.realpath handle it if possible - target = fd.realpath(target) || target - } - } - // Otherwise target is a package name (e.g., github.com/prosperon) + target = shop.resolve_locator(target) } else { // One argument: assume it's a local path, infer package name from cell.toml target = arg1 // Resolve path - if (target == '.' || fd.is_dir(target)) { - target = fd.realpath(target) - } else if (starts_with(target, './') || starts_with(target, '../')) { - target = fd.realpath(target) || target - } + target = shop.resolve_locator(target) // Must be a local path with cell.toml toml_path = target + '/cell.toml' diff --git a/link.cm b/link.cm index b3fba589..71a6cf81 100644 --- a/link.cm +++ b/link.cm @@ -20,30 +20,8 @@ function get_packages_dir() { return global_shop_path + '/packages' } -// return the safe path for the package -function safe_package_path(pkg) { - // For absolute paths, replace / with _ to create a valid directory name - if (pkg && starts_with(pkg, '/')) - return replace(replace(pkg, '/', '_'), '@', '_') - return replace(pkg, '@', '_') -} - function get_package_abs_dir(package) { - return get_packages_dir() + '/' + safe_package_path(package) -} - -function ensure_dir(path) { - if (fd.stat(path).isDirectory) return - var parts = array(path, '/') - var current = starts_with(path, '/') ? '/' : '' - var i = 0 - for (i = 0; i < length(parts); i++) { - if (parts[i] == '') continue - current = current + parts[i] + '/' - if (!fd.stat(current).isDirectory) { - fd.mkdir(current) - } - } + return get_packages_dir() + '/' + fd.safe_package_path(package) } // Resolve a link target to its actual path @@ -54,7 +32,7 @@ function resolve_link_target(target) { return target } // Target is another package - resolve to its directory - return get_packages_dir() + '/' + safe_package_path(target) + return get_packages_dir() + '/' + fd.safe_package_path(target) } var Link = {} @@ -194,7 +172,7 @@ Link.sync_one = function(canonical, target, shop) { // Ensure parent directories exist var parent = fd.dirname(target_dir) - ensure_dir(parent) + fd.ensure_dir(parent) // Check current state var current_link = null diff --git a/list.ce b/list.ce index 262f873a..283c6e82 100644 --- a/list.ce +++ b/list.ce @@ -8,11 +8,9 @@ var shop = use('internal/shop') var pkg = use('package') var link = use('link') -var fd = use('fd') var mode = 'local' var target_pkg = null -var resolved = null var i = 0 var deps = null var packages = null @@ -37,13 +35,7 @@ if (args && length(args) > 0) { mode = 'package' target_pkg = args[0] - // Resolve local paths - if (target_pkg == '.' || starts_with(target_pkg, './') || starts_with(target_pkg, '../') || fd.is_dir(target_pkg)) { - resolved = fd.realpath(target_pkg) - if (resolved) { - target_pkg = resolved - } - } + target_pkg = shop.resolve_locator(target_pkg) } } diff --git a/package.cm b/package.cm index 1c21ffc8..6c8f9d9a 100644 --- a/package.cm +++ b/package.cm @@ -6,16 +6,6 @@ var link = use('link') var global_shop_path = runtime.shop_path -// Convert package name to a safe directory name -// For absolute paths (local packages), replace / with _ -// For remote packages, keep slashes as they use nested directories -function safe_package_path(pkg) { - if (!pkg) return pkg - if (starts_with(pkg, '/')) - return replace(replace(pkg, '/', '_'), '@', '_') - return replace(pkg, '@', '_') -} - function get_path(name) { // If name is null, return the current project directory @@ -33,11 +23,11 @@ function get_path(name) if (starts_with(link_target, '/')) return link_target // Otherwise it's another package name, resolve that - return global_shop_path + '/packages/' + replace(replace(link_target, '/', '_'), '@', '_') + return global_shop_path + '/packages/' + fd.safe_package_path(link_target) } // Remote packages use nested directories, so don't transform slashes - return global_shop_path + '/packages/' + replace(name, '@', '_') + return global_shop_path + '/packages/' + fd.safe_package_path(name) } var config_cache = {} diff --git a/remove.ce b/remove.ce index 09f6d76d..a7f31e9b 100644 --- a/remove.ce +++ b/remove.ce @@ -17,7 +17,6 @@ var target_pkg = null var prune = false var dry_run = false var i = 0 -var resolved = null for (i = 0; i < length(args); i++) { if (args[i] == '--prune') { @@ -43,13 +42,7 @@ if (!target_pkg) { $stop() } -// Resolve relative paths to absolute paths -if (target_pkg == '.' || starts_with(target_pkg, './') || starts_with(target_pkg, '../') || fd.is_dir(target_pkg)) { - resolved = fd.realpath(target_pkg) - if (resolved) { - target_pkg = resolved - } -} +target_pkg = shop.resolve_locator(target_pkg) var packages_to_remove = [target_pkg] diff --git a/resolve.ce b/resolve.ce index e67dba7f..c69d787b 100644 --- a/resolve.ce +++ b/resolve.ce @@ -21,7 +21,6 @@ var target_triple = null var show_locked = false var refresh_first = false var i = 0 -var resolved = null for (i = 0; i < length(args); i++) { if (args[i] == '--target' || args[i] == '-t') { @@ -56,12 +55,7 @@ if (!target_locator) { } // Resolve local paths -if (target_locator == '.' || starts_with(target_locator, './') || starts_with(target_locator, '../') || fd.is_dir(target_locator)) { - resolved = fd.realpath(target_locator) - if (resolved) { - target_locator = resolved - } -} +target_locator = shop.resolve_locator(target_locator) // Check if it's a valid package var pkg_dir = null diff --git a/test.ce b/test.ce index 6cfe1a84..0e2e9842 100644 --- a/test.ce +++ b/test.ce @@ -7,6 +7,7 @@ var time = use('time') var json = use('json') var blob = use('blob') var dbg = use('js') +var testlib = use('internal/testlib') // run gc with dbg.gc() @@ -29,23 +30,8 @@ def ACTOR_TEST_TIMEOUT = 30000 // 30 seconds timeout for actor tests var pending_actor_tests = [] var actor_test_results = [] -// Check if current directory is a valid cell package -function is_valid_package(dir) { - var _dir = dir == null ? '.' : dir - return fd.is_file(_dir + '/cell.toml') -} - -// Get current package name from cell.toml or null -function get_current_package_name() { - if (!is_valid_package('.')) return null - var _load = function() { - var config = pkg.load_config(null) - return config.package || 'local' - } disruption { - return 'local' - } - return _load() -} +var is_valid_package = testlib.is_valid_package +var get_current_package_name = testlib.get_current_package_name // Parse arguments // Usage: @@ -192,32 +178,8 @@ if (diff_mode && !run_ast_noopt_fn) { return } -// Diff mode: deep comparison helper -function values_equal(a, b) { - var i = 0 - if (a == b) return true - if (is_null(a) && is_null(b)) return true - if (is_null(a) || is_null(b)) return false - if (is_array(a) && is_array(b)) { - if (length(a) != length(b)) return false - i = 0 - while (i < length(a)) { - if (!values_equal(a[i], b[i])) return false - i = i + 1 - } - return true - } - return false -} - -function describe(val) { - if (is_null(val)) return "null" - if (is_text(val)) return `"${val}"` - if (is_number(val)) return text(val) - if (is_logical(val)) return text(val) - if (is_function(val)) return "" - return "" -} +var values_equal = testlib.values_equal +var describe = testlib.describe // Diff mode: run a test function through noopt and compare var diff_mismatches = 0 @@ -251,31 +213,8 @@ function diff_check(test_name, file_path, opt_fn, noopt_fn) { } } -function ensure_dir(path) { - if (fd.is_dir(path)) return true - - var parts = array(path, '/') - var current = starts_with(path, '/') ? '/' : '' - var i = 0 - for (i = 0; i < length(parts); i++) { - if (parts[i] == '') continue - current = current + parts[i] + '/' - if (!fd.is_dir(current)) - fd.mkdir(current) - } - return true -} - -// Get the directory for a package -function get_pkg_dir(package_name) { - if (!package_name) { - return fd.realpath('.') - } - if (starts_with(package_name, '/')) { - return package_name - } - return shop.get_package_dir(package_name) -} +var ensure_dir = fd.ensure_dir +var get_pkg_dir = testlib.get_pkg_dir // Collect .ce actor tests from a package function collect_actor_tests(package_name, specific_test) { diff --git a/update.ce b/update.ce index ceb395c5..0d1019e3 100644 --- a/update.ce +++ b/update.ce @@ -1,30 +1,28 @@ // cell update [] - Update packages from remote sources // // Usage: -// cell update Update all packages in shop +// cell update Update all packages // cell update . Update current directory package // cell update Update a specific package // // Options: -// --build Run build after updating -// --target Target platform for build (requires --build) +// --target Target platform for build // --follow-links Update link targets instead of origins // --git Run git pull on local packages before updating var shop = use('internal/shop') -var build = use('build') var fd = use('fd') var os = use('internal/os') +var link = use('link') var target_pkg = null -var run_build = false var target_triple = null var follow_links = false var git_pull = false var i = 0 -var resolved = null +var updated = 0 +var packages = null -// Parse arguments for (i = 0; i < length(args); i++) { if (args[i] == '--help' || args[i] == '-h') { log.console("Usage: cell update [] [options]") @@ -32,13 +30,10 @@ for (i = 0; i < length(args); i++) { log.console("Update packages from remote sources.") log.console("") log.console("Options:") - log.console(" --build Run build after updating") - log.console(" --target Target platform for build (requires --build)") + log.console(" --target Target platform for build") log.console(" --follow-links Update link targets instead of origins") log.console(" --git Run git pull on local packages") $stop() - } else if (args[i] == '--build') { - run_build = true } else if (args[i] == '--target' || args[i] == '-t') { if (i + 1 < length(args)) { target_triple = args[++i] @@ -52,33 +47,22 @@ for (i = 0; i < length(args); i++) { git_pull = true } else if (!starts_with(args[i], '-')) { target_pkg = args[i] - // Resolve relative paths to absolute paths - if (target_pkg == '.' || starts_with(target_pkg, './') || starts_with(target_pkg, '../') || fd.is_dir(target_pkg)) { - resolved = fd.realpath(target_pkg) - if (resolved) { - target_pkg = resolved - } - } } } -// Default target if building -if (run_build && !target_triple) { - target_triple = build.detect_host_target() -} +if (target_pkg) + target_pkg = shop.resolve_locator(target_pkg) -var link = use('link') - -function update_and_fetch(pkg) { +function update_one(pkg) { + var effective_pkg = pkg + var link_target = null var lock = shop.load_lock() var old_entry = lock[pkg] var old_commit = old_entry ? old_entry.commit : null - var effective_pkg = pkg - var link_target = null + var info = shop.resolve_package_info(pkg) var new_entry = null var old_str = null - // Handle follow-links option if (follow_links) { link_target = link.get_target(pkg) if (link_target) { @@ -87,80 +71,47 @@ function update_and_fetch(pkg) { } } + // For local packages with --git, pull first + if (git_pull && info == 'local' && fd.is_dir(effective_pkg + '/.git')) { + log.console(" " + effective_pkg + " (git pull)") + os.system('git -C "' + effective_pkg + '" pull') + } + + // Check for update (sets lock entry if changed) new_entry = shop.update(effective_pkg) - if (new_entry) { - if (new_entry.commit) { - old_str = old_commit ? text(old_commit, 0, 8) : "(new)" - log.console(" " + effective_pkg + " " + old_str + " -> " + text(new_entry.commit, 0, 8)) - shop.fetch(effective_pkg) - } else { - // Local package - run git pull if requested - if (git_pull && fd.is_dir(effective_pkg + '/.git')) { - log.console(" " + effective_pkg + " (git pull)") - os.system('git -C "' + effective_pkg + '" pull') - } else { - log.console(" " + effective_pkg + " (local)") - } - } - shop.extract(effective_pkg) - shop.build_package_scripts(effective_pkg) - return effective_pkg + if (new_entry && new_entry.commit) { + old_str = old_commit ? text(old_commit, 0, 8) : "(new)" + log.console(" " + effective_pkg + " " + old_str + " -> " + text(new_entry.commit, 0, 8)) } - return null + + // Sync: fetch, extract, build + shop.sync(effective_pkg, {target: target_triple}) + + return new_entry } -var updated_packages = [] - -var updated = null -var packages = null -var pkg_count = 0 -var pkg = null if (target_pkg) { - updated = update_and_fetch(target_pkg) - if (updated) { - push(updated_packages, updated) + if (update_one(target_pkg)) { log.console("Updated " + target_pkg + ".") } else { log.console(target_pkg + " is up to date.") } } else { packages = shop.list_packages() - pkg_count = length(packages) - log.console("Checking for updates (" + text(pkg_count) + " package" + (pkg_count == 1 ? "" : "s") + ")...") + log.console("Checking for updates (" + text(length(packages)) + " packages)...") - for (i = 0; i < length(packages); i++) { - pkg = packages[i] - if (pkg == 'core') continue + arrfor(packages, function(pkg) { + if (pkg == 'core') return + if (update_one(pkg)) + updated = updated + 1 + }) - updated = update_and_fetch(pkg) - if (updated) { - push(updated_packages, updated) - } - } - - if (length(updated_packages) > 0) { - log.console("Updated " + text(length(updated_packages)) + " package" + (length(updated_packages) == 1 ? "" : "s") + ".") + if (updated > 0) { + log.console("Updated " + text(updated) + " package(s).") } else { log.console("All packages are up to date.") } } -// Run build if requested -if (run_build && length(updated_packages) > 0) { - log.console("") - log.console("Building updated packages...") - - arrfor(updated_packages, function(pkg) { - var _build = function() { - var lib = build.build_dynamic(pkg, target_triple, 'release') - if (lib) - log.console(" Built: " + lib) - } disruption { - log.error(" Failed to build " + pkg) - } - _build() - }) -} - $stop() diff --git a/verify.ce b/verify.ce index cda42bba..5e9d2e30 100644 --- a/verify.ce +++ b/verify.ce @@ -21,7 +21,6 @@ var scope = null var deep = false var target_triple = null var i = 0 -var resolved = null for (i = 0; i < length(args); i++) { if (args[i] == '--deep') { @@ -206,13 +205,7 @@ if (scope == 'shop') { // Single package locator = scope - // Resolve local paths - if (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator)) { - resolved = fd.realpath(locator) - if (resolved) { - locator = resolved - } - } + locator = shop.resolve_locator(locator) if (deep) { // Gather all dependencies