skip relink when content hasn't changed

This commit is contained in:
2026-01-09 11:51:05 -06:00
parent f16586eaa2
commit 5fd29366a6

116
build.cm
View File

@@ -193,24 +193,51 @@ Build.build_package = function(pkg, target = Build.detect_host_target(), exclude
// ============================================================================
// Dynamic library building
// ============================================================================
// Compute link key from all inputs that affect the dylib output
function compute_link_key(objects, ldflags, target_ldflags, target, cc) {
// Sort objects for deterministic hash
var sorted_objects = objects.slice().sort()
// Build a string representing all link inputs
var parts = []
parts.push('target:' + target)
parts.push('cc:' + cc)
for (var i = 0; i < sorted_objects.length; i++) {
// Object paths are content-addressed, so the path itself is the hash
parts.push('obj:' + sorted_objects[i])
}
for (var i = 0; i < ldflags.length; i++) {
parts.push('ldflag:' + ldflags[i])
}
for (var i = 0; i < target_ldflags.length; i++) {
parts.push('target_ldflag:' + target_ldflags[i])
}
return content_hash(parts.join('\n'))
}
// Build a dynamic library for a package
// Output goes to .cell/lib/<package_name>.<ext>
// Dynamic libraries do NOT link against core; undefined symbols are resolved at dlopen time
// Uses content-addressed store + symlink for caching
Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildtype = 'release') {
var objects = Build.build_package(pkg, target, true, buildtype) // exclude main.c
if (objects.length == 0) {
log.console('No C files in ' + pkg)
return null
}
var lib_dir = shop.get_lib_dir()
var store_dir = lib_dir + '/store'
ensure_dir(lib_dir)
ensure_dir(store_dir)
var lib_name = shop.lib_name_for_package(pkg)
var dylib_ext = toolchains[target].system == 'windows' ? '.dll' : (toolchains[target].system == 'darwin' ? '.dylib' : '.so')
var lib_path = lib_dir + '/' + lib_name + dylib_ext
var stable_path = lib_dir + '/' + lib_name + dylib_ext
// Get link flags (with sigil replacement)
var ldflags = replace_sigils_array(pkg_tools.get_flags(pkg, 'LDFLAGS', target))
var target_ldflags = toolchains[target].c_link_args || []
@@ -218,16 +245,49 @@ Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildty
var pkg_dir = shop.get_package_dir(pkg)
var local_dir = get_local_dir()
var tc = toolchains[target]
// Resolve relative -L paths in ldflags for hash computation
var resolved_ldflags = []
for (var i = 0; i < ldflags.length; i++) {
var flag = ldflags[i]
if (flag.startsWith('-L') && !flag.startsWith('-L/')) {
flag = '-L"' + pkg_dir + '/' + flag.substring(2) + '"'
}
resolved_ldflags.push(flag)
}
// Compute link key
var link_key = compute_link_key(objects, resolved_ldflags, target_ldflags, target, cc)
var store_path = store_dir + '/' + lib_name + '-' + link_key + dylib_ext
// Check if already linked in store
if (fd.is_file(store_path)) {
// Ensure symlink points to the store file
if (fd.is_link(stable_path)) {
var current_target = fd.readlink(stable_path)
if (current_target == store_path) {
// Already up to date
return stable_path
}
fd.unlink(stable_path)
} else if (fd.is_file(stable_path)) {
fd.unlink(stable_path)
}
fd.symlink(store_path, stable_path)
return stable_path
}
// Build link command
var cmd_parts = [cc, '-shared', '-fPIC']
// Platform-specific flags for undefined symbols (resolved at dlopen) and size optimization
if (tc.system == 'darwin') {
// Allow undefined symbols - they will be resolved when dlopen'd into the main executable
cmd_parts.push('-undefined', 'dynamic_lookup')
// Dead-strip unused code
cmd_parts.push('-Wl,-dead_strip')
// Set install_name to stable path so runtime finds it correctly
cmd_parts.push('-Wl,-install_name,' + stable_path)
// rpath for .cell/local libraries
cmd_parts.push('-Wl,-rpath,@loader_path/../local')
cmd_parts.push('-Wl,-rpath,' + local_dir)
@@ -243,40 +303,44 @@ Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildty
// Windows DLLs: use --allow-shlib-undefined for mingw
cmd_parts.push('-Wl,--allow-shlib-undefined')
}
// Add .cell/local to library search path
cmd_parts.push('-L"' + local_dir + '"')
for (var i = 0; i < objects.length; i++) {
cmd_parts.push('"' + objects[i] + '"')
}
// Do NOT link against core library - symbols resolved at dlopen time
// Add LDFLAGS (resolve relative -L paths)
for (var i = 0; i < ldflags.length; i++) {
var flag = ldflags[i]
if (flag.startsWith('-L') && !flag.startsWith('-L/')) {
flag = '-L"' + pkg_dir + '/' + flag.substring(2) + '"'
}
cmd_parts.push(flag)
// Add LDFLAGS
for (var i = 0; i < resolved_ldflags.length; i++) {
cmd_parts.push(resolved_ldflags[i])
}
for (var i = 0; i < target_ldflags.length; i++) {
cmd_parts.push(target_ldflags[i])
}
cmd_parts.push('-o', '"' + lib_path + '"')
cmd_parts.push('-o', '"' + store_path + '"')
var cmd_str = cmd_parts.join(' ')
log.console('Linking ' + lib_path)
log.console('Linking ' + lib_name + dylib_ext)
var ret = os.system(cmd_str)
if (ret != 0) {
throw new Error('Linking failed: ' + pkg)
}
return lib_path
// Update symlink to point to the new store file
if (fd.is_link(stable_path)) {
fd.unlink(stable_path)
} else if (fd.is_file(stable_path)) {
fd.unlink(stable_path)
}
fd.symlink(store_path, stable_path)
return stable_path
}
// ============================================================================