362 lines
9.3 KiB
Plaintext
362 lines
9.3 KiB
Plaintext
var package = {}
|
|
var fd = use('fd')
|
|
var toml = use('toml')
|
|
var json = use('json')
|
|
var os = use('os')
|
|
var link = use('link')
|
|
|
|
// Cache for loaded configs to avoid toml re-parsing corruption
|
|
var config_cache = {}
|
|
|
|
// 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
|
|
if (!name)
|
|
return fd.realpath('.')
|
|
// If name is already an absolute path, use it directly
|
|
if (starts_with(name, '/'))
|
|
return name
|
|
|
|
// Check if this package is linked - if so, use the link target directly
|
|
// This avoids symlink-related issues with file reading
|
|
var link_target = link.get_target(name)
|
|
if (link_target) {
|
|
// If link target is a local path, use it directly
|
|
if (starts_with(link_target, '/'))
|
|
return link_target
|
|
// Otherwise it's another package name, resolve that
|
|
return os.global_shop_path + '/packages/' + replace(replace(link_target, '/', '_'), '@', '_')
|
|
}
|
|
|
|
// Remote packages use nested directories, so don't transform slashes
|
|
return os.global_shop_path + '/packages/' + replace(name, '@', '_')
|
|
}
|
|
|
|
package.load_config = function(name)
|
|
{
|
|
var config_path = get_path(name) + '/cell.toml'
|
|
|
|
// Return cached config if available
|
|
if (config_cache[config_path])
|
|
return config_cache[config_path]
|
|
|
|
if (!fd.is_file(config_path)) {
|
|
throw Error(`${config_path} does not exist`)
|
|
}
|
|
|
|
var content = text(fd.slurp(config_path))
|
|
if (!content || length(trim(content)) == 0)
|
|
return {}
|
|
|
|
var result = toml.decode(content)
|
|
if (!result) {
|
|
return {}
|
|
}
|
|
|
|
// Deep copy to avoid toml module's shared state bug and cache it
|
|
result = json.decode(json.encode(result))
|
|
config_cache[config_path] = result
|
|
|
|
return result
|
|
}
|
|
|
|
package.save_config = function(name, config)
|
|
{
|
|
var config_path = get_path(name) + '/cell.toml'
|
|
fd.slurpwrite(config_path, utf8.encode(toml.encode(config)))
|
|
}
|
|
|
|
package.dependencies = function(name)
|
|
{
|
|
return package.load_config(name).dependencies
|
|
}
|
|
|
|
package.find_alias = function(name, locator)
|
|
{
|
|
var config = package.load_config(name)
|
|
if (!config.dependencies) return null
|
|
|
|
var found = null
|
|
arrfor(array(config.dependencies), function(alias) {
|
|
if (config.dependencies[alias] == locator) found = alias
|
|
})
|
|
return found
|
|
}
|
|
|
|
package.alias_to_package = function(name, alias)
|
|
{
|
|
var config = package.load_config(name)
|
|
if (!config.dependencies) return null
|
|
return config.dependencies[alias]
|
|
}
|
|
|
|
// alias is optional
|
|
package.add_dependency = function(name, locator, alias = locator)
|
|
{
|
|
var config = package.load_config(name)
|
|
if (!config.dependencies) config.dependencies = {}
|
|
config.dependencies[alias] = locator
|
|
package.save_config(name, config)
|
|
}
|
|
|
|
// locator can be a locator or alias
|
|
package.remove_dependency = function(name, locator)
|
|
{
|
|
var config = package.load_config(name)
|
|
if (!config.dependencies) return
|
|
|
|
if (config.dependencies[locator])
|
|
delete config.dependencies[locator]
|
|
else {
|
|
var alias = package.find_alias(name, locator)
|
|
if (alias)
|
|
delete config.dependencies[alias]
|
|
}
|
|
package.save_config(name, config)
|
|
}
|
|
|
|
package.find_package_dir = function(file)
|
|
{
|
|
var absolute = fd.realpath(file)
|
|
|
|
var dir = absolute
|
|
if (fd.is_file(dir))
|
|
dir = fd.dirname(dir)
|
|
|
|
while (dir && length(dir) > 0) {
|
|
var toml_path = dir + '/cell.toml'
|
|
if (fd.is_file(toml_path)) {
|
|
return dir
|
|
}
|
|
dir = fd.dirname(dir)
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
// For a given package,
|
|
// checks for an alias in path, and returns
|
|
// { package, path }
|
|
// so package + path is the full path
|
|
// Returns null if no alias is found for the given path
|
|
package.split_alias = function(name, path)
|
|
{
|
|
if (!path || length(path) == 0) {
|
|
return null
|
|
}
|
|
|
|
var parts = array(path, '/')
|
|
var first_part = parts[0]
|
|
|
|
try {
|
|
var config = package.load_config(name)
|
|
if (!config) return null
|
|
|
|
var deps = config.dependencies
|
|
if (deps && deps[first_part]) {
|
|
var dep_locator = deps[first_part]
|
|
var remaining_path = text(array(parts, 1), '/')
|
|
return { package: dep_locator, path: remaining_path }
|
|
}
|
|
} catch (e) {
|
|
// Config doesn't exist or couldn't be loaded
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
package.gather_dependencies = function(name)
|
|
{
|
|
var all_deps = {}
|
|
var visited = {}
|
|
|
|
function gather_recursive(pkg_name) {
|
|
if (visited[pkg_name]) return
|
|
visited[pkg_name] = true
|
|
|
|
var deps = package.dependencies(pkg_name)
|
|
if (!deps) return
|
|
|
|
arrfor(array(deps), function(alias) {
|
|
var locator = deps[alias]
|
|
if (!all_deps[locator]) {
|
|
all_deps[locator] = true
|
|
gather_recursive(locator)
|
|
}
|
|
})
|
|
}
|
|
|
|
gather_recursive(name)
|
|
return array(all_deps)
|
|
}
|
|
|
|
package.list_files = function(pkg) {
|
|
var dir = get_path(pkg)
|
|
|
|
var files = []
|
|
|
|
var walk = function(current_dir, current_prefix) {
|
|
var list = fd.readdir(current_dir)
|
|
if (!list) return
|
|
|
|
for (var i = 0; i < length(list); i++) {
|
|
var item = list[i]
|
|
if (item == '.' || item == '..') continue
|
|
if (starts_with(item, '.')) continue
|
|
|
|
// Skip build directories in root
|
|
|
|
var full_path = current_dir + "/" + item
|
|
var rel_path = current_prefix ? current_prefix + "/" + item : item
|
|
|
|
var st = fd.stat(full_path)
|
|
if (st.isDirectory) {
|
|
walk(full_path, rel_path)
|
|
} else {
|
|
push(files, rel_path)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fd.is_dir(dir)) {
|
|
walk(dir, "")
|
|
}
|
|
return files
|
|
}
|
|
|
|
package.list_modules = function(name) {
|
|
var files = package.list_files(name)
|
|
var modules = []
|
|
for (var i = 0; i < length(files); i++) {
|
|
if (ends_with(files[i], '.cm')) {
|
|
push(modules, text(files[i], 0, -3))
|
|
}
|
|
}
|
|
return modules
|
|
}
|
|
|
|
package.list_programs = function(name) {
|
|
var files = package.list_files(name)
|
|
var programs = []
|
|
for (var i = 0; i < length(files); i++) {
|
|
if (ends_with(files[i], '.ce')) {
|
|
push(programs, text(files[i], 0, -3))
|
|
}
|
|
}
|
|
return programs
|
|
}
|
|
|
|
// Get flags from cell.toml for a package
|
|
// flag_type is 'CFLAGS' or 'LDFLAGS'
|
|
// target is optional (e.g., 'macos_arm64', 'playdate')
|
|
// Returns an array of flag strings
|
|
package.get_flags = function(name, flag_type, target) {
|
|
var config = package.load_config(name)
|
|
var flags = []
|
|
|
|
// Base flags
|
|
if (config.compilation && config.compilation[flag_type]) {
|
|
var base = config.compilation[flag_type]
|
|
flags = array(flags, filter(array(base, /\s+/), function(f) { return length(f) > 0 }))
|
|
}
|
|
|
|
// Target-specific flags
|
|
if (target && config.compilation && config.compilation[target] && config.compilation[target][flag_type]) {
|
|
var target_flags = config.compilation[target][flag_type]
|
|
flags = array(flags, filter(array(target_flags, /\s+/), function(f) { return length(f) > 0 }))
|
|
}
|
|
|
|
return flags
|
|
}
|
|
|
|
// Get all C files for a package, handling target-specific variants
|
|
// Excludes main.c for dynamic builds (when exclude_main is true)
|
|
// Handles patterns like fd.c vs fd_playdate.c
|
|
package.get_c_files = function(name, target, exclude_main) {
|
|
var toolchains = use('toolchains')
|
|
var known_targets = array(toolchains)
|
|
var files = package.list_files(name)
|
|
|
|
// Group files by their base name (without target suffix)
|
|
var groups = {} // base_key -> { generic: file, variants: { target: file } }
|
|
|
|
for (var i = 0; i < length(files); i++) {
|
|
var file = files[i]
|
|
if (!ends_with(file, '.c') && !ends_with(file, '.cpp')) continue
|
|
|
|
var ext = ends_with(file, '.cpp') ? '.cpp' : '.c'
|
|
var base = text(file, 0, -length(ext))
|
|
var name_part = fd.basename(base)
|
|
var dir_part = fd.dirname(base)
|
|
var dir = (dir_part && dir_part != '.') ? dir_part + '/' : ''
|
|
|
|
// Check for target suffix
|
|
var is_variant = false
|
|
var variant_target = null
|
|
var generic_name = name_part
|
|
|
|
for (var t = 0; t < length(known_targets); t++) {
|
|
var suffix = '_' + known_targets[t]
|
|
if (ends_with(name_part, suffix)) {
|
|
is_variant = true
|
|
variant_target = known_targets[t]
|
|
generic_name = text(name_part, 0, -length(suffix))
|
|
break
|
|
}
|
|
}
|
|
|
|
var group_key = dir + generic_name + ext
|
|
if (!groups[group_key]) {
|
|
groups[group_key] = { generic: null, variants: {} }
|
|
}
|
|
|
|
if (is_variant) {
|
|
groups[group_key].variants[variant_target] = file
|
|
} else {
|
|
groups[group_key].generic = file
|
|
}
|
|
}
|
|
|
|
// Select appropriate file from each group
|
|
var result = []
|
|
arrfor(array(groups), function(key) {
|
|
var group = groups[key]
|
|
var selected = null
|
|
|
|
// Prefer target-specific variant if available
|
|
if (target && group.variants[target]) {
|
|
selected = group.variants[target]
|
|
} else if (group.generic) {
|
|
selected = group.generic
|
|
}
|
|
|
|
if (selected) {
|
|
// Skip main.c if requested
|
|
if (exclude_main) {
|
|
var basename = fd.basename(selected)
|
|
if (basename == 'main.c' || starts_with(basename, 'main_')) return
|
|
}
|
|
push(result, selected)
|
|
}
|
|
})
|
|
|
|
return result
|
|
}
|
|
|
|
// Get the absolute path for a package
|
|
package.get_dir = function(name) {
|
|
return get_path(name)
|
|
}
|
|
|
|
return package
|