centralized ensure dir

This commit is contained in:
2026-02-20 15:02:16 -06:00
24 changed files with 341 additions and 949 deletions

92
add.ce
View File

@@ -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")

View File

@@ -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

View File

@@ -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

View File

@@ -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))

View File

@@ -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 = []

View File

@@ -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 <origin> <path>")
@@ -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

49
diff.ce
View File

@@ -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 "<function>"
if (is_array(val)) return `[array length=${text(length(val))}]`
if (is_object(val)) return `{record keys=${text(length(array(val)))}}`
return "<unknown>"
}
var values_equal = testlib.values_equal
var describe = testlib.describe
// Run a single test file through both paths
function diff_test_file(file_path) {

20
fd.cm
View File

@@ -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

100
fetch.ce
View File

@@ -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 <package> - Fetch a specific package
// cell fetch - Sync all packages
// cell fetch <package> - 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()

46
fuzz.ce
View File

@@ -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 "<function>"
return "<other>"
}
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) {

View File

@@ -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)
}

View File

@@ -1,4 +1,4 @@
// cell install <locator> - Install a package to the shop
// cell install <locator> - Install a package and its dependencies
//
// Usage:
// cell install <locator> Install a package and its dependencies
@@ -6,34 +6,23 @@
//
// Options:
// --target <triple> 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 <locator> [options]")
log.console("")
log.console("Options:")
log.console(" --target <triple> 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 <locator> [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 <triple> 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 <locator>")
log.console("Usage: cell install <locator> [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()

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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 "<function>"
if (is_array(val)) return `[array length=${text(length(val))}]`
if (is_object(val)) return `{record keys=${text(length(array(val)))}}`
return "<unknown>"
}
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
}

21
link.ce
View File

@@ -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'

28
link.cm
View File

@@ -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

10
list.ce
View File

@@ -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)
}
}

View File

@@ -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 = {}

View File

@@ -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]

View File

@@ -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

75
test.ce
View File

@@ -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 "<function>"
return "<other>"
}
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) {

121
update.ce
View File

@@ -1,30 +1,28 @@
// cell update [<locator>] - 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 <locator> Update a specific package
//
// Options:
// --build Run build after updating
// --target <triple> Target platform for build (requires --build)
// --target <triple> 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 [<locator>] [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 <triple> Target platform for build (requires --build)")
log.console(" --target <triple> 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()

View File

@@ -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