258 lines
6.8 KiB
Plaintext
258 lines
6.8 KiB
Plaintext
// cell verify [<scope>] - Verify integrity and consistency
|
|
//
|
|
// Usage:
|
|
// cell verify Verify current directory package
|
|
// cell verify . Verify current directory package
|
|
// cell verify <locator> Verify specific package
|
|
// cell verify shop Verify entire shop
|
|
// cell verify world Verify all world roots
|
|
//
|
|
// Options:
|
|
// --deep Traverse full dependency closure
|
|
// --target <triple> Verify builds for specific target
|
|
|
|
var shop = use('internal/shop')
|
|
var pkg = use('package')
|
|
var link = use('link')
|
|
var build = use('build')
|
|
var fd = use('fd')
|
|
|
|
var scope = null
|
|
var deep = false
|
|
var target_triple = null
|
|
|
|
for (var i = 0; i < length(args); i++) {
|
|
if (args[i] == '--deep') {
|
|
deep = true
|
|
} else if (args[i] == '--target' || args[i] == '-t') {
|
|
if (i + 1 < length(args)) {
|
|
target_triple = args[++i]
|
|
} else {
|
|
log.error('--target requires a triple')
|
|
$stop()
|
|
}
|
|
} else if (args[i] == '--help' || args[i] == '-h') {
|
|
log.console("Usage: cell verify [<scope>] [options]")
|
|
log.console("")
|
|
log.console("Verify integrity and consistency.")
|
|
log.console("")
|
|
log.console("Scopes:")
|
|
log.console(" <locator> Verify specific package")
|
|
log.console(" shop Verify entire shop")
|
|
log.console(" world Verify all world roots")
|
|
log.console("")
|
|
log.console("Options:")
|
|
log.console(" --deep Traverse full dependency closure")
|
|
log.console(" --target <triple> Verify builds for specific target")
|
|
$stop()
|
|
} else if (!starts_with(args[i], '-')) {
|
|
scope = args[i]
|
|
}
|
|
}
|
|
|
|
// Default to current directory
|
|
if (!scope) {
|
|
scope = '.'
|
|
}
|
|
|
|
// Detect target if not specified
|
|
if (!target_triple) {
|
|
target_triple = build.detect_host_target()
|
|
}
|
|
|
|
var errors = []
|
|
var warnings = []
|
|
var checked = 0
|
|
|
|
function add_error(msg) {
|
|
push(errors, msg)
|
|
}
|
|
|
|
function add_warning(msg) {
|
|
push(warnings, msg)
|
|
}
|
|
|
|
// Verify a single package
|
|
function verify_package(locator) {
|
|
checked++
|
|
|
|
var lock = shop.load_lock()
|
|
var lock_entry = lock[locator]
|
|
var links = link.load()
|
|
var link_target = links[locator]
|
|
|
|
// Check lock entry exists
|
|
if (!lock_entry) {
|
|
add_error(locator + ": not in lock")
|
|
}
|
|
|
|
// Check package directory exists
|
|
var pkg_dir = shop.get_package_dir(locator)
|
|
var dir_exists = fd.is_dir(pkg_dir) || fd.is_link(pkg_dir)
|
|
|
|
if (!dir_exists) {
|
|
add_error(locator + ": package directory missing at " + pkg_dir)
|
|
return
|
|
}
|
|
|
|
// Check cell.toml exists
|
|
if (!fd.is_file(pkg_dir + '/cell.toml')) {
|
|
add_error(locator + ": missing cell.toml")
|
|
return
|
|
}
|
|
|
|
// For linked packages, verify link target
|
|
if (link_target) {
|
|
if (starts_with(link_target, '/')) {
|
|
// Local path target
|
|
if (!fd.is_dir(link_target)) {
|
|
add_error(locator + ": link target does not exist: " + link_target)
|
|
} else if (!fd.is_file(link_target + '/cell.toml')) {
|
|
add_error(locator + ": link target is not a valid package: " + link_target)
|
|
}
|
|
} else {
|
|
// Package target
|
|
var target_dir = shop.get_package_dir(link_target)
|
|
if (!fd.is_dir(target_dir) && !fd.is_link(target_dir)) {
|
|
add_error(locator + ": link target package not found: " + link_target)
|
|
}
|
|
}
|
|
|
|
// Check symlink is correct
|
|
if (fd.is_link(pkg_dir)) {
|
|
var current_target = fd.readlink(pkg_dir)
|
|
var expected_target = starts_with(link_target, '/') ? link_target : shop.get_package_dir(link_target)
|
|
if (current_target != expected_target) {
|
|
add_warning(locator + ": symlink target mismatch (expected " + expected_target + ", got " + current_target + ")")
|
|
}
|
|
} else {
|
|
add_warning(locator + ": linked but directory is not a symlink")
|
|
}
|
|
}
|
|
|
|
// Check build output exists
|
|
var lib_dir = shop.get_lib_dir()
|
|
var lib_name = shop.lib_name_for_package(locator)
|
|
var dylib_ext = '.dylib' // TODO: detect from target
|
|
var lib_path = lib_dir + '/' + lib_name + dylib_ext
|
|
|
|
// Only check for builds if package has C files
|
|
try {
|
|
var c_files = pkg.get_c_files(locator, target_triple, true)
|
|
if (c_files && length(c_files) > 0) {
|
|
if (!fd.is_file(lib_path)) {
|
|
add_warning(locator + ": library not built at " + lib_path)
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// Skip build check if can't determine C files
|
|
}
|
|
}
|
|
|
|
// Check for link cycles
|
|
function check_link_cycles() {
|
|
var links = link.load()
|
|
|
|
function follow_chain(origin, visited) {
|
|
if (visited[origin]) {
|
|
return origin // cycle detected
|
|
}
|
|
visited[origin] = true
|
|
|
|
var target = links[origin]
|
|
if (target && links[target]) {
|
|
return follow_chain(target, visited)
|
|
}
|
|
return null
|
|
}
|
|
|
|
arrfor(links, function(origin) {
|
|
var cycle_start = follow_chain(origin, {})
|
|
if (cycle_start) {
|
|
add_error("Link cycle detected starting from: " + origin)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Check for dangling links
|
|
function check_dangling_links() {
|
|
var links = link.load()
|
|
|
|
arrfor(array(links), function(origin) {
|
|
var target = links[origin]
|
|
if (starts_with(target, '/')) {
|
|
if (!fd.is_dir(target)) {
|
|
add_error("Dangling link: " + origin + " -> " + target + " (target does not exist)")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// Gather packages to verify
|
|
var packages_to_verify = []
|
|
|
|
if (scope == 'shop') {
|
|
packages_to_verify = shop.list_packages()
|
|
} else if (scope == 'world') {
|
|
// For now, world is the same as shop
|
|
// In future, this could be a separate concept
|
|
packages_to_verify = shop.list_packages()
|
|
} else {
|
|
// Single package
|
|
var locator = scope
|
|
|
|
// Resolve local paths
|
|
if (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator)) {
|
|
var resolved = fd.realpath(locator)
|
|
if (resolved) {
|
|
locator = resolved
|
|
}
|
|
}
|
|
|
|
if (deep) {
|
|
// Gather all dependencies
|
|
var all_deps = pkg.gather_dependencies(locator)
|
|
push(packages_to_verify, locator)
|
|
arrfor(all_deps, function(dep) {
|
|
push(packages_to_verify, dep)
|
|
})
|
|
} else {
|
|
push(packages_to_verify, locator)
|
|
}
|
|
}
|
|
|
|
log.console("Verifying " + text(length(packages_to_verify)) + " package(s)...")
|
|
log.console("")
|
|
|
|
// Run verification
|
|
check_link_cycles()
|
|
check_dangling_links()
|
|
|
|
arrfor(packages_to_verify, function(p) {
|
|
if (p == 'core') return
|
|
verify_package(p)
|
|
})
|
|
|
|
// Print results
|
|
if (length(warnings) > 0) {
|
|
log.console("Warnings:")
|
|
arrfor(warnings, function(w) {
|
|
log.console(" " + w)
|
|
})
|
|
log.console("")
|
|
}
|
|
|
|
if (length(errors) > 0) {
|
|
log.console("Errors:")
|
|
arrfor(errors, function(e) {
|
|
log.console(" " + e)
|
|
})
|
|
log.console("")
|
|
log.console("Verification FAILED: " + text(length(errors)) + " error(s), " + text(length(warnings)) + " warning(s)")
|
|
// Note: would use process.exit(1) if available
|
|
} else {
|
|
log.console("Verification PASSED: " + text(checked) + " package(s) checked, " + text(length(warnings)) + " warning(s)")
|
|
}
|
|
|
|
$stop()
|