skip relink when content hasn't changed

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

View File

@@ -193,9 +193,34 @@ 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
@@ -205,11 +230,13 @@ Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildty
}
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))
@@ -219,6 +246,37 @@ Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildty
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']
@@ -228,6 +286,8 @@ Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildty
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)
@@ -253,30 +313,34 @@ Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildty
// 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
}
// ============================================================================