var package = {} var fd = use('fd') var toml = use('toml') var os = use('os') function get_path(name) { return os.global_shop_path + '/packages/' + name } package.load_config = function(name) { var config_path = get_path(name) + '/cell.toml' if (!fd.is_file(config_path)) throw new Error(`${config_path} isn't a path`) return toml.decode(text(fd.slurp(config_path))) } 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