From ae619c6a78df928c49f81577cb97f3750f9c84a3 Mon Sep 17 00:00:00 2001 From: DuProcess <273172371+DuProcess@users.noreply.github.com> Date: Sun, 12 Jul 2026 21:34:25 -0400 Subject: [PATCH] Skip stale latest prebuilts in installers --- install.ps1 | 82 ++++++++++++++++++++++++++++++++++++++++ install.sh | 70 ++++++++++++++++++++++++++++++++++ tests/release_scripts.rs | 21 ++++++++++ 3 files changed, 173 insertions(+) diff --git a/install.ps1 b/install.ps1 index ad23a7e..9fc7e85 100644 --- a/install.ps1 +++ b/install.ps1 @@ -100,6 +100,85 @@ function Release-LatestTagDownloadUrl { return $null } +function Version-Parts($Value) { + [regex]::Matches($Value, "\d+") | ForEach-Object { [int64]$_.Value } +} + +function Compare-DoshVersion($Left, $Right) { + $leftParts = @(Version-Parts $Left) + $rightParts = @(Version-Parts $Right) + $width = [Math]::Max($leftParts.Count, $rightParts.Count) + for ($i = 0; $i -lt $width; $i++) { + $l = if ($i -lt $leftParts.Count) { $leftParts[$i] } else { 0 } + $r = if ($i -lt $rightParts.Count) { $rightParts[$i] } else { 0 } + if ($l -lt $r) { return -1 } + if ($l -gt $r) { return 1 } + } + return 0 +} + +function Current-SourceVersion { + if (Test-Path "Cargo.toml") { + $raw = Get-Content "Cargo.toml" -Raw + } else { + $web = Repo-WebBase $Repo + if (-not $web) { + return $null + } + try { + $raw = (Invoke-WebRequest -UseBasicParsing -Uri "$web/raw/branch/main/Cargo.toml").Content + } + catch { + return $null + } + } + if ($raw -match '(?m)^version = "([^"]+)"') { + return $Matches[1] + } + return $null +} + +function Latest-ReleaseTag { + $web = Repo-WebBase $Repo + if (-not $web) { + return $null + } + try { + $response = Invoke-WebRequest -UseBasicParsing -Uri "$web/releases/latest" -MaximumRedirection 5 + $effective = $null + if ($response.BaseResponse.ResponseUri) { + $effective = $response.BaseResponse.ResponseUri.AbsoluteUri + } elseif ($response.BaseResponse.RequestMessage -and $response.BaseResponse.RequestMessage.RequestUri) { + $effective = $response.BaseResponse.RequestMessage.RequestUri.AbsoluteUri + } + $prefix = "$web/releases/tag/" + if ($effective -and $effective.StartsWith($prefix)) { + return $effective.Substring($prefix.Length) + } + } + catch { + return $null + } + return $null +} + +function Latest-ReleaseIsStale { + if ($BinaryUrl -or $BinaryBase -or $BinaryVersion -ne "latest") { + return $false + } + $latest = Latest-ReleaseTag + $current = Current-SourceVersion + if (-not $latest -or -not $current) { + return $false + } + $latestVersion = $latest.TrimStart("v") + if ((Compare-DoshVersion $latestVersion $current) -lt 0) { + Write-Warning "latest release $latestVersion is older than source $current; skipping stale prebuilt" + return $true + } + return $false +} + function Verify-ArchiveChecksum($Url, $Archive) { $checksumPath = "$Archive.sha256" try { @@ -160,6 +239,9 @@ $configDir = Join-Path $HOME ".config\dosh" New-Item -ItemType Directory -Force -Path $bindir, $configDir | Out-Null function Install-Prebuilt { + if (Latest-ReleaseIsStale) { + return $false + } $url = Release-LatestTagDownloadUrl if (-not $url) { $url = Release-DownloadUrl diff --git a/install.sh b/install.sh index d0367b9..eed4e44 100755 --- a/install.sh +++ b/install.sh @@ -222,6 +222,73 @@ release_latest_tag_download_url() { esac } +version_part() { + version="$1" + index="$2" + printf '%s\n' "$version" \ + | sed 's/[^0-9][^0-9]*/ /g' \ + | awk -v index="$index" '{ value=$index; if (value == "") value=0; print value }' +} + +version_less_than() { + left="$1" + right="$2" + for index in 1 2 3 4 5 6 7 8; do + lpart="$(version_part "$left" "$index")" + rpart="$(version_part "$right" "$index")" + if [ "$lpart" -lt "$rpart" ]; then + return 0 + fi + if [ "$lpart" -gt "$rpart" ]; then + return 1 + fi + done + return 1 +} + +current_source_version() { + if [ -f Cargo.toml ]; then + sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | sed -n '1p' + return 0 + fi + [ -n "$repo" ] || return 1 + web_base="$(repo_web_base "$repo")" || return 1 + curl -fsSL "$web_base/raw/branch/main/Cargo.toml" 2>/dev/null \ + | sed -n 's/^version = "\(.*\)"/\1/p' \ + | sed -n '1p' +} + +latest_release_tag() { + [ -n "$repo" ] || return 1 + web_base="$(repo_web_base "$repo")" || return 1 + latest_url="$(curl -fsSL -o /dev/null -w '%{url_effective}' "$web_base/releases/latest" 2>/dev/null || true)" + case "$latest_url" in + "$web_base"/releases/tag/*) + tag="${latest_url##"$web_base"/releases/tag/}" + [ -n "$tag" ] || return 1 + printf '%s\n' "$tag" + ;; + *) + return 1 + ;; + esac +} + +latest_release_is_stale() { + if [ -n "$binary_url" ] || [ -n "$binary_base" ] || [ "$binary_version" != "latest" ]; then + return 1 + fi + latest="$(latest_release_tag || true)" + current="$(current_source_version || true)" + [ -n "$latest" ] && [ -n "$current" ] || return 1 + latest="${latest#v}" + if version_less_than "$latest" "$current"; then + echo "latest release $latest is older than source $current; skipping stale prebuilt" >&2 + return 0 + fi + return 1 +} + find_extracted_binary() { find "$1" -type f -name "$2" 2>/dev/null | sed -n '1p' } @@ -312,6 +379,9 @@ verify_archive_version() { } try_install_prebuilt() { + if latest_release_is_stale; then + return 1 + fi download_url="$(release_latest_tag_download_url || release_download_url)" || return 1 tmpdir="$(mktemp -d)" archive="$tmpdir/$(release_artifact_name)" diff --git a/tests/release_scripts.rs b/tests/release_scripts.rs index e40d27f..276d4a8 100644 --- a/tests/release_scripts.rs +++ b/tests/release_scripts.rs @@ -19,6 +19,27 @@ fn release_scripts_skip_stale_artifacts_when_auto_discovering() { assert!(publish.contains("actual=\"$(artifact_version \"$artifact\" || true)\"")); } +#[test] +fn installers_skip_stale_latest_release_prebuilts() { + let install = include_str!("../install.sh"); + assert!(install.contains("latest_release_is_stale")); + assert!(install.contains("current_source_version")); + assert!(install.contains("latest release $latest is older than source $current")); + assert!( + install.contains("if latest_release_is_stale; then"), + "unix installer must skip stale latest before downloading a prebuilt" + ); + + let ps1 = include_str!("../install.ps1"); + assert!(ps1.contains("Latest-ReleaseIsStale")); + assert!(ps1.contains("Current-SourceVersion")); + assert!(ps1.contains("latest release $latestVersion is older than source $current")); + assert!( + ps1.contains("if (Latest-ReleaseIsStale)"), + "windows installer must skip stale latest before downloading a prebuilt" + ); +} + #[test] fn package_check_verifies_only_artifacts_it_builds() { let makefile = include_str!("../Makefile");