fixing shop

This commit is contained in:
2025-12-09 00:48:59 -06:00
parent 5c5427fdd9
commit de4c9c724e
4 changed files with 299 additions and 313 deletions

View File

@@ -8,6 +8,7 @@
CELL_SHOP = $(HOME)/.cell
CELL_CORE = $(CELL_SHOP)/core
CELL_CORE_PACKAGE = $(CELL_SHOP)/packages/core
cell: libcell_runtime.dylib cell_main
cp cell_main cell
@@ -26,10 +27,14 @@ cell_main: source/main.c libcell_runtime.dylib
cc -o cell_main source/main.c -L. -lcell_runtime -Wl,-rpath,@loader_path -Wl,-rpath,/opt/homebrew/lib
# Install core: symlink this directory to ~/.cell/core
install: $(CELL_SHOP)
install: bootstrap $(CELL_SHOP)
@echo "Linking cell core to $(CELL_CORE)"
rm -rf $(CELL_CORE)
rm -rf $(CELL_CORE_PACKAGE)
ln -s $(PWD) $(CELL_CORE)
ln -s $(PWD) $(CELL_CORE_PACKAGE)
cp cell /opt/homebrew/bin/
cp libcell_runtime.dylib /opt/homebrew/lib/
@echo "Core installed."
# Create the cell shop directories
@@ -49,7 +54,7 @@ static:
# Bootstrap: build cell from scratch using meson (only needed once)
# Also installs core scripts to ~/.cell/core
bootstrap: install
bootstrap:
meson setup build_bootstrap -Dbuildtype=release
meson compile -C build_bootstrap
cp build_bootstrap/cell .

View File

@@ -12,9 +12,9 @@ A package can be a gitea url, like gitea.pockle.world/john/prosperon, which effe
Or it can be a local absolute path (on computers with a file system), like .cell/packages/User/john/work/prosperon, which is a symlink to the actual directory.
Cell itself is stored in .cell/core, and can build itself from there. Updating cell involves getting cell from somewhere, and rebuilding it. The cell core is itself a package.
Cell itself is stored as a package in .cell/packages/<core>, where <core> is specified in shop.toml (e.g., `core = "bootstrap"` for local development, or `core = "gitea.pockle.world/john/cell"` for a remote package). Updating cell involves getting cell from somewhere, and rebuilding it. The cell core is itself a package.
When an actor or module is requested, it's loaded first from the package, if not found, it checks in packages, and if not found there, it checks in the cell core scripts (.cell/core/scripts).
When an actor or module is requested, it's loaded first from the package, if not found, it checks in packages (via aliases in cell.toml dependencies), and if not found there, it checks in the core package.
Packages can declare aliases for packages, so the "accio" package can say "prosperon = gitea.pockle.world/john/prosperon", and then load "prosperon/sprite".
A module in prosperon can simply load "sprite" to load its own sprite.
@@ -37,7 +37,7 @@ the cell shop looks like this:
.cell
shop.toml <---- shop configuration
packages
gitea.pockle.world/john/cell <--- this is the root cell
gitea.pockle.world/john/cell <--- this is the core cell
gitea.pockle.world/john/prosperon
cell.toml <--- the manifest of the package
mod1.cm
@@ -86,7 +86,11 @@ this is more suitable for development. firstly, the cell core must build a share
Then, each package compiles, linking to the cell core shared library. Modules are written to include the cell core headers.
Shared libraries are stored
Shared libraries are stored in the build directory, hashed with the content hash
When update is run, it copies the dynamic libraries into .cell/shared, with their package name; ie, the dynamic library for the gitea.pockle.world/john/prosperon would be at .cell/shared/gitea_pockle_world_john_prosperon.dylib.
There will be commands to gather them all together; so you can request "all dylibs for platform x", and it will give you a folder of them.
### bootstrapping cell
after the cell shared libraries are all compiled, the main.c of the cell core is compiled against the cell core shared library, creating a thin cell runner. However, even on subsequent updates to cell, this runner is not updated; only for bootstrapping.
@@ -94,4 +98,4 @@ after the cell shared libraries are all compiled, the main.c of the cell core is
### static binary
For static binaries, rather than going through and compiling each dynamic library for a package, a static binary is created for a particular package, based on its dependencies. All C symbols, plus the main.c runner from the cell core, are packaged into one binary. It works just like the basic cell program.
However, you can also specify an entry point for this binary, for example, a particular actor. When the binary is run, that particular actor runs.
However, you can also specify an entry point for this binary, for example, a particular actor. When the binary is run, that particular actor runs.

View File

@@ -752,7 +752,8 @@ actor_mod.setname(cell.args.program)
var prog = cell.args.program
// Resolve the main program path
var locator = shop.resolve_locator(cell.args.program, ACTOR_EXT, null)
var locator = shop.resolve_locator(cell.args.program + ".ce", null)
if (!locator)
throw new Error(`Main program ${cell.args.program} could not be found`)

586
shop.cm
View File

@@ -10,16 +10,30 @@ var utf8 = use('utf8')
var blob = use('blob')
var build_utils = use('build')
var core = "gitea.pockle.world/john/cell"
var core = "core"
function pull_from_cache(content)
{
var path = hash_path(content)
if (fd.is_file(path))
return fd.slurp(path)
}
function put_into_cache(content, obj)
{
var path = hash_path(content)
fd.slurpwrite(path, obj)
}
function content_hash(content)
{
return text(crypto.blake2(utf8.encode(content)), 'h')
return text(crypto.blake2(content), 'h')
}
// a package string is what is used to import a module, like prosperon/sprite
// in prosperon/sprite, sprite is the module, and prosperon is the package (usually, an alias)
// a canonical package name relates prosperon to its source, like gitea.pockle.world/john/prosperon
function hash_path(content)
{
return get_global_build_dir() + '/' + content_hash(content)
}
var Shop = {}
@@ -100,11 +114,16 @@ Shop.find_package_dir = function(file_path) {
return null
}
function slurp_package_file(file, package)
{
return fd.slurp(get_packages_dir() + '/' + package + '/' + file)
}
// Link a local package into the shop
function ensure_package_link(abs_path) {
if (!abs_path || !abs_path.startsWith('/')) return false
var packages_dir = get_modules_dir()
var packages_dir = get_packages_dir()
var target_link = packages_dir + abs_path
// If link already exists and points to correct place, we are good
@@ -196,7 +215,7 @@ function get_lock_path() {
}
// Get the packages directory (in the global shop)
function get_modules_dir() {
function get_packages_dir() {
return global_shop_path + '/packages'
}
@@ -215,6 +234,11 @@ Shop.get_core_dir = function() {
return global_shop_path + '/core'
}
function get_core_package()
{
return "gitea.pockle.world/john/cell"
}
// Get the links file path (in the global shop)
function get_links_path() {
return global_shop_path + '/link.toml'
@@ -259,7 +283,7 @@ Shop.file_info = function(file) {
}
// Check if file is in a package (in global shop packages dir)
var packages_prefix = get_modules_dir() + '/'
var packages_prefix = get_packages_dir() + '/'
if (file.startsWith(packages_prefix)) {
var rest = file.substring(packages_prefix.length)
@@ -318,38 +342,71 @@ function get_import_name(path)
return parts.slice(1).join('/')
}
// given a path, get a full package import
// ie, 'prosperon/sprite' would return 'gitea.pockle.world/john/prosperon/sprite'
// if prosperon were a dependency
function get_path_in_package(path, ctx)
function get_aliased_path(path, pkg)
{
var pkg = get_import_package(path)
var mod_name = get_import_name(path)
if (!pkg) return null
var canon_pkg = get_canonical_package(pkg, ctx)
return canon_pkg + "/" + mod_name
}
// Given a path like 'prosperon/sprite', extract the package alias ('prosperon')
// and resolve it to its canonical path using the dependencies in ctx's config.
// Returns the canonical package path (e.g., 'gitea.pockle.world/john/prosperon')
// or null if the path has no package prefix or the package is not found.
function get_normalized_package(path, ctx)
function validate_package_name(pkg)
{
var pkg = get_import_package(path)
if (!pkg) return null
return get_canonical_package(pkg, ctx)
if (!pkg) return false
// Check for unsafe filesystem characters (except @ and . which are allowed)
// Disallow: : \ ? * " < > | and control characters
if (/[:\\\?\*"<>\|]/.test(pkg)) return false
// Disallow control characters (0-31)
for (var i = 0; i < pkg.length; i++) {
var code = pkg.charCodeAt(i)
if (code < 32) return false
}
// Disallow empty segments or trailing/leading slashes
if (pkg.startsWith('/') || pkg.endsWith('/')) return false
if (pkg.includes('//')) return false
// Disallow . and .. as path segments
var parts = pkg.split('/')
for (var i = 0; i < parts.length; i++) {
if (parts[i] == '' || parts[i] == '.' || parts[i] == '..') return false
}
return true
}
// taking the package into account, find the canonical name
function get_canonical_package(mod, ctx) {
// return the safe path for the package
// guaranteed to be validated
function get_package_path(pkg)
{
return pkg.replace('@', '_')
}
function get_shared_lib_path()
{
return get_global_build_dir() + '/' + 'lib'
}
function get_aliased_package(alias, pkg)
{
if (alias.split('/').length > 1)
throw new Error(`alias ${alias} is invalid, as it contains slashes`)
var cfg = Shop.load_config(pkg)
if (!cfg.dependencies)
return null
return cfg.dependencies[alias]
}
// taking the package ctx into account, find the canonical name
function get_canonical_package(alias, ctx) {
var cfg = Shop.load_config(ctx)
if (!cfg || !cfg.dependencies)
return null
var pkg = cfg.dependencies[mod]
var pkg = cfg.dependencies[alias]
if (!pkg)
return null
@@ -364,7 +421,7 @@ function get_import_dl(name) {
var pkg = get_import_package(name)
if (!pkg) return null
if (open_dl[pkg]) return open_dl[pkg]
var dlpath = get_modules_dir() + '/' + pkg + '/' + pkg + dylib_ext
var dlpath = get_packages_dir() + '/' + pkg + '/' + pkg + dylib_ext
var dl = os.dylib_open(dlpath)
if (dl) {
open_dl[pkg] = dl
@@ -396,7 +453,7 @@ Shop.load_config = function(module) {
content = fd.slurp(config_path)
} else {
// Module config is at <modules_dir>/<module>/cell.toml
var module_path = get_modules_dir() + '/' + module + '/cell.toml'
var module_path = get_packages_dir() + '/' + module + '/cell.toml'
if (!fd.is_file(module_path))
return null
@@ -453,30 +510,6 @@ Shop.load_lock = function() {
if (!content.length) return {}
var lock = toml.decode(content)
var changed = false
// Clean lock file entries
for (var key in lock) {
var entry = lock[key]
if (entry && entry.package && entry.package.includes('://')) {
var parts = entry.package.split('://')
entry.package = parts[1]
changed = true
}
// Also clean keys if they are locators/packages with protocols
if (key.includes('://')) {
var parts = key.split('://')
var new_key = parts[1]
lock[new_key] = entry
delete lock[key]
changed = true
}
}
if (changed) {
Shop.save_lock(lock)
}
return lock
}
@@ -539,7 +572,14 @@ Shop.clear_links = function() {
return true
}
// given a package, return the path in the shop
function package_shop_path(pkg)
{
}
// Parse module package string (e.g., "git.world/jj/mod@v0.6.3")
// returns {path, name, version}
Shop.parse_package = function(pkg) {
Shop.verify_package_name(pkg)
var path = pkg
@@ -609,7 +649,7 @@ Shop.resolve_path_to_package = function(path_str) {
var abs = fd.realpath(path_str)
if (!abs) return null
var modules_dir = get_modules_dir()
var modules_dir = get_packages_dir()
// Case 1: Path is inside the modules directory (e.g. downloaded package)
if (abs.startsWith(modules_dir + '/')) {
@@ -732,7 +772,7 @@ Shop.get_module_dir = function(alias) {
var parsed = Shop.parse_package(pkg)
if (!parsed) return null
return get_modules_dir() + '/' + parsed.path
return get_packages_dir() + '/' + parsed.path
}
function lock_package(loc)
@@ -779,26 +819,19 @@ function get_flags(config, platform, key) {
Shop.get_flags = get_flags
// Resolve module function
// Resolve module function, hashing it in the process
function resolve_mod_fn(path, pkg) {
if (!fd.is_file(path)) throw new Error(`path ${path} is not a file`)
var content = fd.slurp(path)
var hash = content_hash(content)
var hash_path = get_global_build_dir() + '/' + hash
if (fd.is_file(hash_path)) {
var obj = fd.slurp(hash_path)
var obj = pull_from_cache(content)
if (obj) {
var fn = js.compile_unblob(obj)
return js.eval_compile(fn)
}
var form = script_form
// We don't really use pkg scope for compilation anymore since it's just hash based cache
// But we need to pass a package context for the 'use' function inside the module
var script = form(path, text(content), pkg);
// Compile name is just for debug/stack traces
@@ -806,84 +839,74 @@ function resolve_mod_fn(path, pkg) {
var fn = js.compile(compile_name, script)
// Ensure build dir exists
ensure_dir(get_global_build_dir())
fd.slurpwrite(hash_path, js.compile_blob(fn))
put_into_cache(content, js.compile_blob(fn))
return js.eval_compile(fn)
}
// resolve_core_mod_fn is no longer needed as core modules are just modules in a package (or local)
function resolve_locator(path, ext, ctx)
// given a path and a package context
// return module info about where it was found
function resolve_locator(path, ctx)
{
if (path.endsWith(ext)) ext = ''
// 1. Check local file (relative to current directory if no context, or relative to package 'ctx' if provided)
// If ctx is provided, it's a package alias or path.
var local_path
if (ctx) {
var mod_dir = get_modules_dir()
// Check if ctx is an absolute path (local package)
if (ctx.startsWith('/')) {
local_path = ctx + '/' + path + ext
} else {
local_path = mod_dir + '/' + ctx + '/' + path + ext
}
} else {
// No context, just check simple local path
local_path = path + ext
// 1. If no context, resolve from core only
if (!ctx) {
var core_dir = Shop.get_core_dir()
var core_file_path = core_dir + '/' + path
if (fd.is_file(core_file_path)) {
var fn = resolve_mod_fn(core_file_path, 'core')
return {path: core_file_path, scope: SCOPE_CORE, symbol: fn}
}
return null
}
if (fd.is_file(local_path)) {
var fn = resolve_mod_fn(local_path, ctx)
return {path: local_path, scope: SCOPE_LOCAL, symbol:fn}
} else {
// Fallback: check if the path itself is a file (ignoring required extension)
// This allows running scripts like 'test.cm' even if engine asks for '.ce'
if (ext && path != local_path && fd.is_file(path)) {
var fn = resolve_mod_fn(path, ctx)
return {path: path, scope: SCOPE_LOCAL, symbol:fn}
// check in ctx package
var ctx_path = get_packages_dir() + '/' + get_package_path(ctx) + '/' + path
if (fd.is_file(ctx_path)) {
var fn = resolve_mod_fn(ctx_path, ctx)
return {path: ctx_path, scope: SCOPE_LOCAL, symbol: fn}
}
// check for aliased dependency
var alias = path.split('/')
if (alias.length > 1) {
var alias_pkg = get_aliased_package(alias[0], ctx)
if (alias_pkg) {
var alias_path = get_packages_dir() + '/' + get_package_path(alias_pkg) + '/' + alias.slice(1).join('/')
if (fd.is_file(alias_path)){
var fn = resolve_mod_fn(alias_path, ctx)
return {path: alias_path, scope:SCOPE_PACKAGE, symbol:fn}
}
}
}
// 2. Check installed packages (if path suggests a package import)
// This handles imports like 'prosperon/sprite'
var canonical_pkg = get_normalized_package(path, ctx)
if (canonical_pkg) {
var pkg_path = get_path_in_package(path, ctx)
var mod_path = get_modules_dir() + '/' + pkg_path + ext
if (fd.is_file(mod_path)) {
var fn = resolve_mod_fn(mod_path, canonical_pkg)
return {path: mod_path, scope: SCOPE_PACKAGE, symbol:fn}
}
var package_path = get_packages_dir() + '/' + get_package_path(path)
if (fd.is_file(package_path)) {
var fn = resolve_mod_fn(package_path, ctx)
return {path: package_path, scope: SCOPE_PACKAGE, symbol: fn}
}
// 3. Check core (as a fallback)
// "core" is now just another package, potentially.
// But if the user really wants to load "time", "js", etc, which are in core.
// We can try to resolve them in the core package.
// Ideally 'core' is defined in dependencies if needed, or we hardcode a fallback.
// Hardcoded fallback for now to match behavior:
// 4. Check core as fallback
var core_dir = Shop.get_core_dir()
var core_file_path = core_dir + '/' + path + ext
var core_file_path = core_dir + '/' + path
if (fd.is_file(core_file_path)) {
// Core is treated as a package now, essentially
var fn = resolve_mod_fn(core_file_path, 'core') // using 'core' string as package for now
return {path: path + ext, scope: SCOPE_CORE, symbol:fn};
var fn = resolve_mod_fn(core_file_path, 'core')
return {path: core_file_path, scope: SCOPE_CORE, symbol: fn}
}
return null;
return null
}
// given a C path like 'foo/bar/baz.c', return 'foo_bar_baz_use'
function c_sym_path(path)
{
return path.replace(/\//g, '_').replace(/\\/g, '_').replace(/\./g, '_').replace(/-/g, '_')
return path.substring(0, path.lastIndexOf('.')).replace(/\//g, '_').replace(/\\/g, '_').replace(/\./g, '_').replace(/-/g, '_') + '_use'
}
function c_sym_prefix(package)
{
'js_' + package.replace(/\//g, '_').replace(/\./g, '_').replace(/-/g, '_') + '_'
}
function resolve_c_symbol(path, package_context)
@@ -967,7 +990,7 @@ function resolve_c_symbol(path, package_context)
// 2. Check if valid package import (e.g. 'prosperon/sprite')
var pkg_alias = get_import_package(path)
if (pkg_alias) {
var canon_pkg = get_normalized_package(path, package_context)
var canon_pkg = get_aliased_package(path, package_context)
if (canon_pkg) {
var mod_name = get_import_name(path)
var mod_sym = mod_name.replace(/\//g, '_').replace(/-/g, '_').replace(/\./g, '_')
@@ -986,7 +1009,7 @@ function resolve_c_symbol(path, package_context)
// Then try dynamic library for package (skip in static_only mode)
if (!static_only) {
// Check package dir for libcellmod
var pkg_build_dir = get_modules_dir() + '/' + canon_pkg
var pkg_build_dir = get_packages_dir() + '/' + canon_pkg
var dl_path = pkg_build_dir + '/libcellmod' + dylib_ext
if (fd.is_file(dl_path)) {
if (!open_dls[dl_path]) open_dls[dl_path] = os.dylib_open(dl_path)
@@ -1021,7 +1044,7 @@ function resolve_c_symbol(path, package_context)
function resolve_module_info(path, package_context) {
var c_resolve = resolve_c_symbol(path, package_context) || {scope:999}
var mod_resolve = resolve_locator(path, '.cm', package_context) || {scope:999}
var mod_resolve = resolve_locator(path + '.cm', package_context) || {scope:999}
var min_scope = Math.min(c_resolve.scope, mod_resolve.scope)
if (min_scope == 999)
@@ -1218,7 +1241,7 @@ Shop.update = function(pkg) {
var lock = Shop.load_lock()
var parsed = Shop.parse_package(pkg)
var info = Shop.resolve_package_info(pkg)
var target_dir = get_modules_dir() + '/' + parsed.path
var target_dir = get_packages_dir() + '/' + parsed.path
var result = info.type == 'local'
? update_local(pkg, info, target_dir)
@@ -1422,7 +1445,7 @@ Shop.remove = function(alias_or_path) {
target_pkg = resolved
} else {
// Check if alias_or_path exists directly in modules dir?
var direct_path = get_modules_dir() + '/' + alias_or_path
var direct_path = get_packages_dir() + '/' + alias_or_path
if (fd.exists(direct_path)) {
target_pkg = alias_or_path
}
@@ -1434,7 +1457,7 @@ Shop.remove = function(alias_or_path) {
return false
}
var target_dir = get_modules_dir() + '/' + target_pkg
var target_dir = get_packages_dir() + '/' + target_pkg
// Remove from lock
var lock = Shop.load_lock()
@@ -1504,7 +1527,7 @@ Shop.remove_replacement = function(alias) {
Shop.list_files = function(pkg) {
var dir
if (!pkg) dir = '.'
else dir = get_modules_dir() + '/' + pkg
else dir = get_packages_dir() + '/' + pkg
var files = []
@@ -1518,8 +1541,6 @@ Shop.list_files = function(pkg) {
if (item.startsWith('.')) continue
// Skip build directories in root
if (!pkg && (item == 'build' || item == 'build_dbg' || item == 'build_release' || item == 'build_web' || item == 'build_fast')) continue
if (!pkg && item == 'cell_modules') continue // Just in case
var full_path = current_dir + "/" + item
var rel_path = current_prefix ? current_prefix + "/" + item : item
@@ -1599,15 +1620,96 @@ function get_core_build_dir() {
return get_global_build_dir() + '/core'
}
function get_package_scripts(package)
{
var files = Shop.list_files(package)
var scripts = []
for (var i = 0; i < files.length; i++) {
var file = files[i]
if (file.endsWith('.cm') || file.endsWith('.ce')) {
scripts.push(file)
}
}
return scripts
}
function get_package_c_files(package)
{
var files = Shop.list_files(package)
var c_files = []
for (var i = 0; i < files.length; i++) {
var file = files[i]
if (file.endsWith('.c') || file.endsWith('.cpp')) {
c_files.push(file)
}
}
return c_files
}
Shop.build_package_scripts = function(package)
{
// compiles all .ce and .cm files in a package
var scripts = get_package_scripts(package)
for (var script of scripts)
resolve_mod_fn(script, package)
}
function get_target_dynamic_files(package, target)
{
var files = get_package_c_files(package)
var result = []
// Build a map of base names to their variants
var variants = {}
for (var i = 0; i < files.length; i++) {
var file = files[i]
var basename = file.substring(0, file.lastIndexOf('.'))
var ext = file.substring(file.lastIndexOf('.'))
// Check if this is a target-specific variant (name_target.ext)
var underscore = basename.lastIndexOf('_')
if (underscore >= 0) {
var base = basename.substring(0, underscore)
var suffix = basename.substring(underscore + 1)
if (!variants[base]) variants[base] = {}
variants[base][suffix] = file
} else {
if (!variants[basename]) variants[basename] = {}
variants[basename]['default'] = file
}
}
// Select appropriate files
for (var base in variants) {
var v = variants[base]
// Skip main files entirely for dynamic builds
if (base == 'main' || base.endsWith('/main')) continue
if (target && v[target]) {
result.push(v[target])
} else if (v['default']) {
result.push(v['default'])
}
}
return result
}
Shop.build_package = function(package)
{
if (package == 'local') package = null
if (package) package = Shop.parse_package(package).path
Shop.build_package_scripts(package)
var dynamic_files = get_target_dynamic_files(package, os.platform())
var files = Shop.list_files(package)
var build_dir = get_build_dir(package)
ensure_dir(build_dir)
var module_dir = package ? get_modules_dir() + '/' + package : (current_package_path || '.')
var module_dir = package ? get_packages_dir() + '/' + package : (current_package_path || '.')
log.console(`Building package ${package ? package : 'local'} to ${build_dir}`)
@@ -1616,9 +1718,7 @@ Shop.build_package = function(package)
var cflags = get_flags(config, platform, 'CFLAGS')
// Determine usage prefix for C symbols
var use_prefix
if (!package) use_prefix = 'js_local_'
else use_prefix = 'js_' + package.replace(/\//g, '_').replace(/\./g, '_').replace(/-/g, '_') + '_'
var use_prefix = c_sym_prefix(package)
var c_objects = []
@@ -1627,38 +1727,10 @@ Shop.build_package = function(package)
for (var i=0; i<files.length; i++) {
var file = files[i]
// Skip main*.c files - they're only for static builds
if (is_main_file(file)) continue
var src_path
if (!package) {
src_path = (current_package_path || '.') + '/' + file
} else if (package.startsWith('/')) {
// Local absolute package
src_path = package + '/' + file
} else {
// Regular package
src_path = get_modules_dir() + '/' + package + '/' + file
}
if (file.endsWith('.cm') || file.endsWith('.ce')) {
// Compile module
try {
resolve_mod_fn(src_path, package)
} catch (e) {
log.error(`Failed to compile ${src_path}: ${e}`)
log.error(e)
return false
}
} else if (file.endsWith('.c') || file.endsWith('.cpp')) {
// Compile C
var obj_path = build_dir + '/' + file + '.o'
var meta_path = obj_path + '.meta'
ensure_dir(obj_path.substring(0, obj_path.lastIndexOf('/')))
var safe_path = c_sym_path(file.substring(0, file.lastIndexOf('.')))
var use_name = use_prefix + safe_path + '_use'
var use_name = use_prefix + c_sym_path(file)
var comp_src = file
var comp_obj = file + '.o'
var base_cmd = 'cc -fPIC '
@@ -1666,74 +1738,11 @@ Shop.build_package = function(package)
if (cflags != '') compile_flags += ' ' + cflags
var full_compile_cmd = 'cd ' + module_dir + ' && ' + base_cmd + compile_flags + ' -o ' + comp_obj
var cmd_hash = get_hash(full_compile_cmd)
var file_content = slurp_package_file(file, package)
var obj_path = hash_path(full_compile_cmd + text(file_content))
var needs_compile = true
var meta = null
if (fd.is_file(obj_path) && fd.is_file(meta_path)) {
try {
meta = json.decode(text(fd.slurp(meta_path)))
} catch(e) {}
if (meta && meta.cmd_hash == cmd_hash) {
var st_src = fd.stat(src_path)
var st_obj = fd.stat(obj_path)
if (st_src && st_obj && st_src.mtime <= st_obj.mtime) {
needs_compile = false
// Check headers
if (meta.headers) {
for (var h = 0; h < meta.headers.length; h++) {
var header_rel = meta.headers[h]
var header_full
if (header_rel.startsWith('/'))
header_full = header_rel
else
header_full = module_dir + '/' + header_rel
if (!fd.is_file(header_full)) {
log.console(`coulnd't find header ${header_full}`)
needs_compile = true; break;
}
var st_h = fd.stat(header_full)
if (st_h.mtime > st_obj.mtime) {
log.console(`${header_full} out of date`)
needs_compile = true; break;
}
}
}
}
}
}
if (needs_compile) {
if (fd.is_file(obj_path)) {
log.console("Compiling " + src_path + " -> " + obj_path)
// 1. Generate dependencies
var deps_file = comp_obj + '.d'
// Use same flags but with -M
var deps_cmd = 'cd ' + module_dir + ' && ' + base_cmd + '-MM ' + compile_flags + ' > ' + deps_file
os.system(deps_cmd)
var headers = []
var deps_full_path = module_dir + '/' + deps_file
if (fd.is_file(deps_full_path)) {
var deps_content = text(fd.slurp(deps_full_path))
deps_content = deps_content.replace(/\\\n/g, ' ').replace(/\\\r\n/g, ' ').replace(/\n/g, ' ')
var parts = deps_content.split(' ')
for (var p=0; p<parts.length; p++) {
var part = parts[p].trim()
if (part == '' || part.endsWith(':')) continue
if (part == comp_src) continue
headers.push(part)
}
fd.rm(deps_full_path)
}
// 2. Compile
var ret = os.system(full_compile_cmd)
if (ret != 0) {
log.error("Compilation failed for " + src_path)
@@ -1742,82 +1751,49 @@ Shop.build_package = function(package)
// 3. Move object
os.system('mv ' + module_dir + '/' + comp_obj + ' ' + obj_path)
// 4. Write meta
var new_meta = {
cmd_hash: cmd_hash,
headers: headers
}
fd.slurpwrite(meta_path, utf8.encode(json.encode(new_meta)))
}
c_objects.push(obj_path)
}
}
// Link if there are C objects
if (c_objects.length > 0) {
var lib_name = build_dir + '/libcellmod' + dylib_ext
var lib_meta_path = lib_name + '.meta'
var link_flags = '-fPIC -shared'
// Link against core cellmod.dylib
var core_build = get_core_build_dir()
if (platform == 'macOS') {
link_flags += ' -L' + core_build + ' -Wl,-rpath,@loader_path/../../core'
} else if (platform == 'Linux' || platform == 'linux') {
link_flags += ' -L' + core_build + ' -Wl,-rpath,$ORIGIN/../../core'
} else if (platform == 'Windows') {
link_flags += ' -L' + core_build
}
link_flags += ' -lcellmod'
var ldflags = get_flags(config, platform, 'LDFLAGS')
if (ldflags != '') link_flags += ' ' + ldflags
var temp_lib = 'libcellmod' + dylib_ext
var objs_str = ''
for (var i=0; i<c_objects.length; i++) {
objs_str += '"' + c_objects[i] + '" '
}
var lib_name = get_shared_lib_path() + '/lib' + get_package_path(package) + dylib_ext
var link_cmd = 'cd ' + module_dir + ' && cc ' + link_flags + ' ' + objs_str + ' -lc -lc++ -o ' + temp_lib
var link_cmd_hash = get_hash(link_cmd)
var link_flags = '-fPIC -shared'
// Link against core package dylib (unless we ARE the core)
var core_pkg = get_core_package()
if (package != core_pkg) {
var lib_dir = get_shared_lib_path()
if (platform == 'macOS') {
link_flags += ' -L' + lib_dir + ' -Wl,-rpath,@loader_path'
} else if (platform == 'Linux' || platform == 'linux') {
link_flags += ' -L' + lib_dir + ' -Wl,-rpath,$ORIGIN'
} else if (platform == 'Windows') {
link_flags += ' -L' + lib_dir
}
link_flags += ' -l' + get_core_package()
}
var ldflags = get_flags(config, platform, 'LDFLAGS')
if (ldflags != '') link_flags += ' ' + ldflags
var objs_str = ''
for (var i=0; i<c_objects.length; i++) {
objs_str += '"' + c_objects[i] + '" '
}
// Check if we need to relink
var needs_link = true
if (fd.is_file(lib_name) && fd.is_file(lib_meta_path)) {
var meta = null
try {
meta = json.decode(text(fd.slurp(lib_meta_path)))
} catch(e) {}
if (meta && meta.cmd_hash == link_cmd_hash) {
var lib_time = fd.stat(lib_name).mtime
needs_link = false
for (var i=0; i<c_objects.length; i++) {
if (fd.stat(c_objects[i]).mtime > lib_time) {
needs_link = true
break
}
}
}
}
if (needs_link) {
log.console("Linking " + lib_name)
var ret = os.system(link_cmd)
if (ret != 0) {
log.error("Linking failed")
return false
}
os.system('mv ' + module_dir + '/' + temp_lib + ' ' + lib_name)
fd.slurpwrite(lib_meta_path, utf8.encode(json.encode({ cmd_hash: link_cmd_hash })))
}
log.console("Built " + lib_name)
var link_cmd = 'cc ' + link_flags + ' ' + objs_str + ' -lc -lc++ -o ' + lib_name
log.console("Linking " + lib_name)
var ret = os.system(link_cmd)
if (ret != 0) {
log.error("Linking failed")
return false
}
log.console("Built " + lib_name)
}
return true
@@ -1897,7 +1873,7 @@ Shop.resolve_module = function(module_name, package_name, is_file_fn) {
// If we're in a package context, check the package first
if (package_name) {
var pkg_path = get_modules_dir() + '/' + package_name + '/' + module_name + '.cm'
var pkg_path = get_packages_dir() + '/' + package_name + '/' + module_name + '.cm'
if (is_file_fn(pkg_path)) {
return { path: pkg_path, package_name: package_name }
}
@@ -1916,7 +1892,7 @@ Shop.resolve_module = function(module_name, package_name, is_file_fn) {
var parsed = Shop.parse_package(pkg)
var canonical_path = parsed.path
var dep_path = get_modules_dir() + '/' + canonical_path + '/' + sub_module + '.cm'
var dep_path = get_packages_dir() + '/' + canonical_path + '/' + sub_module + '.cm'
if (is_file_fn(dep_path)) {
return { path: dep_path, package_name: pkg_alias }
}
@@ -1942,7 +1918,7 @@ Shop.resolve_module = function(module_name, package_name, is_file_fn) {
var parsed = Shop.parse_package(pkg)
var canonical_path = parsed.path
var dep_path = get_modules_dir() + '/' + canonical_path + '/' + module_name + '.cm'
var dep_path = get_packages_dir() + '/' + canonical_path + '/' + module_name + '.cm'
if (is_file_fn(dep_path)) {
return { path: dep_path, package_name: alias }
}