8ce8656de2
El SDK CI - dev / build-and-test (pull_request) Successful in 7m33s
epm's sibling modules (registry/install/update) call functions defined in other modules and in the El runtime (config, read_installed, registry_find, manifest_deps, manifest_name, registry_latest_version, registry_token, install_vessel, installed_version) without importing them, so elc emits no C prototype for those calls. gcc<=13 treated the resulting implicit declarations as warnings; gcc>=14 and clang reject them as hard errors, which is why the "Build epm" CI step fails and blocks the whole dev/stage pipeline. Add `extern fn` forward declarations -- El's own separate-compilation mechanism -- for each cross-module callee at the top of registry/install/update. This gives elc the correct C prototype in every generated translation unit, so the calls compile cleanly and still resolve at link time. Simply suppressing -Wimplicit-function-declaration would be unsafe: an implicit int return truncates the 64-bit pointer returns of config/registry_find into a latent crash, so declaring the true signatures is the correct fix. Localized to epm; touches neither elc nor the runtime.
217 lines
8.6 KiB
EmacsLisp
217 lines
8.6 KiB
EmacsLisp
// epm/src/update.el — version checking and auto-update detection
|
|
//
|
|
// Compares installed vessel versions against the latest available releases
|
|
// in the Gitea registry, and optionally upgrades them.
|
|
//
|
|
// Depends on: registry.el (registry_latest_version, registry_find),
|
|
// install.el (read_installed, install_vessel, installed_version)
|
|
|
|
// ── Cross-module forward declarations ─────────────────────────────────────────
|
|
// Defined in sibling epm modules; resolved at link time. The `extern fn` decls
|
|
// give elc the C prototypes so generated update.c compiles cleanly under strict
|
|
// compilers (gcc>=14 / clang) that reject implicit function declarations.
|
|
extern fn read_installed() -> String // install.el
|
|
extern fn installed_version(name: String) -> String // install.el
|
|
extern fn install_vessel(name: String, version: String) -> Bool // install.el
|
|
extern fn registry_latest_version(name: String) -> String // registry.el
|
|
|
|
// ── Semver helpers ────────────────────────────────────────────────────────────
|
|
|
|
// semver_part extracts the Nth dot-separated component from a semver string.
|
|
// Returns "0" if the component is missing.
|
|
fn semver_part(ver: String, idx: Int) -> String {
|
|
let parts: [String] = str_split(ver, ".")
|
|
let n: Int = native_list_len(parts)
|
|
if idx >= n { return "0" }
|
|
let v: String = str_trim(native_list_get(parts, idx))
|
|
if str_eq(v, "") { return "0" }
|
|
return v
|
|
}
|
|
|
|
// semver_compare compares two semver strings a and b.
|
|
// Returns -1 if a < b, 0 if a == b, 1 if a > b.
|
|
// Compares major, then minor, then patch numerically.
|
|
fn semver_compare(a: String, b: String) -> Int {
|
|
let a_major: Int = native_str_to_int(semver_part(a, 0))
|
|
let b_major: Int = native_str_to_int(semver_part(b, 0))
|
|
if a_major < b_major { return -1 }
|
|
if a_major > b_major { return 1 }
|
|
|
|
let a_minor: Int = native_str_to_int(semver_part(a, 1))
|
|
let b_minor: Int = native_str_to_int(semver_part(b, 1))
|
|
if a_minor < b_minor { return -1 }
|
|
if a_minor > b_minor { return 1 }
|
|
|
|
let a_patch: Int = native_str_to_int(semver_part(a, 2))
|
|
let b_patch: Int = native_str_to_int(semver_part(b, 2))
|
|
if a_patch < b_patch { return -1 }
|
|
if a_patch > b_patch { return 1 }
|
|
|
|
return 0
|
|
}
|
|
|
|
// semver_gt returns true if a is strictly greater than b.
|
|
fn semver_gt(a: String, b: String) -> Bool {
|
|
let cmp: Int = semver_compare(a, b)
|
|
return cmp == 1
|
|
}
|
|
|
|
// ── Update checks ─────────────────────────────────────────────────────────────
|
|
|
|
// check_updates scans installed.json and checks if newer versions are available
|
|
// in the registry. Prints a notice for each outdated vessel.
|
|
// Returns the count of outdated vessels.
|
|
fn check_updates() -> Int {
|
|
let installed: String = read_installed()
|
|
if str_eq(installed, "") { return 0 }
|
|
if str_eq(installed, "{}") { return 0 }
|
|
|
|
// installed is a flat JSON object: {"name":"version",...}
|
|
// We need to iterate over its keys. Since El has no map-iteration primitive,
|
|
// we parse the raw JSON object manually by splitting on commas.
|
|
let inner_len: Int = str_len(installed)
|
|
if inner_len < 2 { return 0 }
|
|
// Strip outer braces
|
|
let body: String = str_slice(installed, 1, inner_len - 1)
|
|
let body_trimmed: String = str_trim(body)
|
|
if str_eq(body_trimmed, "") { return 0 }
|
|
|
|
let pairs: [String] = str_split(body_trimmed, ",")
|
|
let pair_count: Int = native_list_len(pairs)
|
|
let outdated: Int = 0
|
|
|
|
let i: Int = 0
|
|
while i < pair_count {
|
|
let pair: String = str_trim(native_list_get(pairs, i))
|
|
// pair looks like "name":"version"
|
|
let colon: Int = str_index_of(pair, ":")
|
|
if colon > 0 {
|
|
let raw_key: String = str_trim(str_slice(pair, 0, colon))
|
|
let raw_val: String = str_trim(str_slice(pair, colon + 1, str_len(pair)))
|
|
// Strip surrounding quotes
|
|
let key_len: Int = str_len(raw_key)
|
|
let val_len: Int = str_len(raw_val)
|
|
let vessel_name: String = raw_key
|
|
let vessel_ver: String = raw_val
|
|
if str_starts_with(raw_key, "\"") {
|
|
let vessel_name = str_slice(raw_key, 1, key_len - 1)
|
|
}
|
|
if str_starts_with(raw_val, "\"") {
|
|
let vessel_ver = str_slice(raw_val, 1, val_len - 1)
|
|
}
|
|
|
|
// Check latest version in registry
|
|
let latest: String = registry_latest_version(vessel_name)
|
|
if !str_eq(latest, "") {
|
|
if semver_gt(latest, vessel_ver) {
|
|
println(" " + vessel_name + ": " + vessel_ver + " -> " + latest + " available")
|
|
let outdated = outdated + 1
|
|
}
|
|
}
|
|
}
|
|
let i = i + 1
|
|
}
|
|
|
|
return outdated
|
|
}
|
|
|
|
// ── Update command ────────────────────────────────────────────────────────────
|
|
|
|
// cmd_update fetches the latest version of all installed vessels (or a specific
|
|
// one) and installs it if newer than what's currently installed.
|
|
// If vessel_name is "" it updates all installed vessels.
|
|
fn cmd_update(vessel_name: String) -> Void {
|
|
if !str_eq(vessel_name, "") {
|
|
// Update a single named vessel
|
|
let current: String = installed_version(vessel_name)
|
|
if str_eq(current, "") {
|
|
println("epm: '" + vessel_name + "' is not installed")
|
|
exit(1)
|
|
}
|
|
|
|
let latest: String = registry_latest_version(vessel_name)
|
|
if str_eq(latest, "") {
|
|
println("epm: no releases found for '" + vessel_name + "' in registry")
|
|
return
|
|
}
|
|
|
|
if !semver_gt(latest, current) {
|
|
println("epm: " + vessel_name + " " + current + " is already up to date")
|
|
return
|
|
}
|
|
|
|
println("epm: updating " + vessel_name + " " + current + " -> " + latest + " ...")
|
|
let ok: Bool = install_vessel(vessel_name, latest)
|
|
if !ok {
|
|
println("epm: update failed for " + vessel_name)
|
|
exit(1)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Update all installed vessels
|
|
let installed: String = read_installed()
|
|
if str_eq(installed, "") {
|
|
println("epm: no installed vessels")
|
|
return
|
|
}
|
|
if str_eq(installed, "{}") {
|
|
println("epm: no installed vessels")
|
|
return
|
|
}
|
|
|
|
let inner_len: Int = str_len(installed)
|
|
if inner_len < 2 {
|
|
println("epm: no installed vessels")
|
|
return
|
|
}
|
|
let body: String = str_slice(installed, 1, inner_len - 1)
|
|
let body_trimmed: String = str_trim(body)
|
|
if str_eq(body_trimmed, "") {
|
|
println("epm: no installed vessels")
|
|
return
|
|
}
|
|
|
|
let pairs: [String] = str_split(body_trimmed, ",")
|
|
let pair_count: Int = native_list_len(pairs)
|
|
let updated_count: Int = 0
|
|
|
|
let i: Int = 0
|
|
while i < pair_count {
|
|
let pair: String = str_trim(native_list_get(pairs, i))
|
|
let colon: Int = str_index_of(pair, ":")
|
|
if colon > 0 {
|
|
let raw_key: String = str_trim(str_slice(pair, 0, colon))
|
|
let raw_val: String = str_trim(str_slice(pair, colon + 1, str_len(pair)))
|
|
let key_len: Int = str_len(raw_key)
|
|
let val_len: Int = str_len(raw_val)
|
|
let vessel_name2: String = raw_key
|
|
let vessel_ver2: String = raw_val
|
|
if str_starts_with(raw_key, "\"") {
|
|
let vessel_name2 = str_slice(raw_key, 1, key_len - 1)
|
|
}
|
|
if str_starts_with(raw_val, "\"") {
|
|
let vessel_ver2 = str_slice(raw_val, 1, val_len - 1)
|
|
}
|
|
|
|
let latest: String = registry_latest_version(vessel_name2)
|
|
if !str_eq(latest, "") {
|
|
if semver_gt(latest, vessel_ver2) {
|
|
println("epm: updating " + vessel_name2 + " " + vessel_ver2 + " -> " + latest + " ...")
|
|
let ok: Bool = install_vessel(vessel_name2, latest)
|
|
if ok {
|
|
let updated_count = updated_count + 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let i = i + 1
|
|
}
|
|
|
|
if updated_count == 0 {
|
|
println("epm: all installed vessels are up to date")
|
|
} else {
|
|
println("epm: updated " + native_int_to_str(updated_count) + " vessel(s)")
|
|
}
|
|
}
|