Files
cell/package.cm
2026-01-09 11:49:22 -06:00

334 lines
8.5 KiB
Plaintext

var package = {}
var fd = use('fd')
var toml = use('toml')
var os = use('os')
// 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 (pkg.startsWith('/'))
return pkg.replaceAll('/', '_').replaceAll('@', '_')
return pkg.replaceAll('@', '_')
}
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 (name.startsWith('/'))
return name
// Remote packages use nested directories, so don't transform slashes
return os.global_shop_path + '/packages/' + name.replaceAll('@', '_')
}
package.load_config = function(name)
{
var config_path = get_path(name) + '/cell.toml'
if (!fd.is_file(config_path))
throw new Error(`${config_path} does not exist`)
var content = text(fd.slurp(config_path))
if (!content || content.trim().length == 0)
return {}
return toml.decode(content)
}
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
for (var alias in config.dependencies)
if (config.dependencies[alias] == locator)
return alias
return null
}
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)) {
var last_slash = dir.lastIndexOf('/')
if (last_slash > 0) dir = dir.substring(0, last_slash)
}
while (dir && dir.length > 0) {
var toml_path = dir + '/cell.toml'
if (fd.is_file(toml_path)) {
return dir
}
var last_slash = dir.lastIndexOf('/')
if (last_slash <= 0) break
dir = dir.substring(0, last_slash)
}
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 || path.length == 0) {
return null
}
var parts = path.split('/')
var first_part = parts[0]
var config = package.load_config(name)
if (config.dependencies && config.dependencies[first_part]) {
var dep_locator = config.dependencies[first_part]
var remaining_path = parts.slice(1).join('/')
return { package: dep_locator, path: remaining_path }
}
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
for (var alias in deps) {
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 < list.length; i++) {
var item = list[i]
if (item == '.' || item == '..') continue
if (item.startsWith('.')) 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 {
files.push(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 < files.length; i++) {
if (files[i].endsWith('.cm')) {
modules.push(files[i].substring(0, files[i].length - 3))
}
}
return modules
}
package.list_programs = function(name) {
var files = package.list_files(name)
var programs = []
for (var i = 0; i < files.length; i++) {
if (files[i].endsWith('.ce')) {
programs.push(files[i].substring(0, files[i].length - 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 = flags.concat(base.split(/\s+/).filter(function(f) { return f.length > 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 = flags.concat(target_flags.split(/\s+/).filter(function(f) { return f.length > 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 < files.length; i++) {
var file = files[i]
if (!file.endsWith('.c') && !file.endsWith('.cpp')) continue
var ext = file.endsWith('.cpp') ? '.cpp' : '.c'
var base = file.substring(0, file.length - ext.length)
var dir = ''
var name_part = base
var slash = base.lastIndexOf('/')
if (slash >= 0) {
dir = base.substring(0, slash + 1)
name_part = base.substring(slash + 1)
}
// Check for target suffix
var is_variant = false
var variant_target = null
var generic_name = name_part
for (var t = 0; t < known_targets.length; t++) {
var suffix = '_' + known_targets[t]
if (name_part.endsWith(suffix)) {
is_variant = true
variant_target = known_targets[t]
generic_name = name_part.substring(0, name_part.length - suffix.length)
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 = []
for (var key in groups) {
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 = selected
var s = selected.lastIndexOf('/')
if (s >= 0) basename = selected.substring(s + 1)
if (basename == 'main.c' || basename.startsWith('main_')) continue
}
result.push(selected)
}
}
return result
}
// Get the absolute path for a package
package.get_dir = function(name) {
return get_path(name)
}
return package