diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13a558f..896caf5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: outputs: version: ${{ steps.version.outputs.version }} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - name: Verify workspace versions are aligned run: ./scripts/check-version-sync.sh - id: version @@ -35,7 +35,7 @@ jobs: name: Vendor Sync runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 with: submodules: true - name: Verify surge-core vendor snapshot @@ -46,7 +46,7 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'pull_request' steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - run: rustup component add rustfmt - run: cargo fmt --all -- --check @@ -54,7 +54,7 @@ jobs: name: maintainability runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - name: Run maintainability guardrail run: ./scripts/check-maintainability.sh @@ -62,7 +62,7 @@ jobs: name: clippy runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 with: submodules: true - uses: Swatinem/rust-cache@v2 @@ -76,7 +76,7 @@ jobs: name: clippy strict runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 with: submodules: true - uses: Swatinem/rust-cache@v2 @@ -90,7 +90,7 @@ jobs: name: clippy pedantic (advisory) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 with: submodules: true - uses: Swatinem/rust-cache@v2 @@ -121,7 +121,7 @@ jobs: name: macOS cache_key: macos-test steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 with: submodules: true - uses: Swatinem/rust-cache@v2 @@ -181,7 +181,7 @@ jobs: target/release/surge-installer-ui target/release/surge-supervisor steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 with: submodules: true - uses: actions/cache@v6 @@ -228,7 +228,7 @@ jobs: run: working-directory: dotnet steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 with: submodules: true - uses: actions/setup-dotnet@v5 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c60e445..6816808 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,6 @@ env: permissions: contents: write - packages: write jobs: validate-release: @@ -24,7 +23,7 @@ jobs: release_version: ${{ steps.validate.outputs.release_version }} is_prerelease: ${{ steps.validate.outputs.is_prerelease }} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 with: ref: ${{ github.event.release.tag_name }} fetch-depth: 0 @@ -124,7 +123,7 @@ jobs: target/release/surge-installer-ui target/release/surge-supervisor steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 with: ref: ${{ github.event.release.tag_name }} submodules: true @@ -175,7 +174,7 @@ jobs: run: working-directory: dotnet steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 with: ref: ${{ github.event.release.tag_name }} submodules: true @@ -189,8 +188,8 @@ jobs: restore-keys: nuget- - name: Pack run: dotnet pack Surge.NET/Surge.NET.csproj --configuration Release -p:Version=${{ needs.validate-release.outputs.release_version }} --output ../nupkgs - - name: Push to GitHub Packages - run: dotnet nuget push ../nupkgs/*.nupkg --source https://nuget.pkg.github.com/fintermobilityas/index.json --api-key ${{ secrets.GITHUB_TOKEN }} --skip-duplicate + - name: Push to NuGet + run: dotnet nuget push ../nupkgs/*.nupkg --source nuget.org --api-key ${{ secrets.PETERSUNDE_NUGET_ORG_API_KEY }} --skip-duplicate publish-cargo-crates-io: name: Publish Cargo (crates.io) @@ -199,7 +198,7 @@ jobs: env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 with: ref: ${{ github.event.release.tag_name }} submodules: true @@ -235,7 +234,7 @@ jobs: needs: [validate-release, build-artifacts] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 with: ref: ${{ github.event.release.tag_name }} diff --git a/Cargo.lock b/Cargo.lock index 552b330..003e88e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -343,9 +343,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.65" +version = "1.2.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" dependencies = [ "find-msvc-tools", "jobserver", diff --git a/crates/surge-cli/src/commands/setup.rs b/crates/surge-cli/src/commands/setup.rs index e7d3bb0..cf8b350 100644 --- a/crates/surge-cli/src/commands/setup.rs +++ b/crates/surge-cli/src/commands/setup.rs @@ -8,7 +8,7 @@ use surge_core::error::{Result, SurgeError}; use surge_core::install::{self as core_install, InstallProfile}; use surge_core::installer_package::{ InstallerPackageAcquisition, ResolveInstallerPackageOptions, ResolvedInstallerPackage, install_artifact_cache_dir, - prune_install_artifact_cache_with_stats, resolve_installer_package, retained_artifacts_for_resolved_install_cache, + prune_install_artifact_cache, resolve_installer_package, retained_artifacts_for_install_cache_without_index, }; use surge_core::platform::fs::make_executable; use surge_core::platform::paths::default_install_root; @@ -379,17 +379,23 @@ fn ensure_stage_cache_entry( } fn prune_setup_artifact_cache(install_root: &Path, package: &ResolvedPackage, manifest: &InstallerManifest) { - let Some(retained_artifacts) = retained_artifacts_for_resolved_install_cache(package, manifest) else { - return; + let owned_retained_artifacts; + let retained_artifacts = match package.retained_artifacts.as_ref() { + Some(retained_artifacts) => retained_artifacts, + None => match retained_artifacts_for_install_cache_without_index(manifest) { + Some(retained_artifacts) => { + owned_retained_artifacts = retained_artifacts; + &owned_retained_artifacts + } + None => return, + }, }; - match prune_install_artifact_cache_with_stats(install_root, retained_artifacts.as_ref()) { - Ok(result) if result.pruned_artifact_count == 0 => {} - Ok(result) => { + match prune_install_artifact_cache(install_root, retained_artifacts) { + Ok(0) => {} + Ok(pruned) => { logline::info(&format!( - "Pruned {} stale artifact cache entr{}; retained policy key count: {}.", - result.pruned_artifact_count, - if result.pruned_artifact_count == 1 { "y" } else { "ies" }, - result.retained_policy_key_count + "Pruned {pruned} stale artifact cache entr{}.", + if pruned == 1 { "y" } else { "ies" } )); } Err(e) => { diff --git a/crates/surge-core/src/installer_package.rs b/crates/surge-core/src/installer_package.rs index 5023fee..8fb429b 100644 --- a/crates/surge-core/src/installer_package.rs +++ b/crates/surge-core/src/installer_package.rs @@ -1,4 +1,3 @@ -use std::borrow::Cow; use std::collections::BTreeSet; use std::path::{Path, PathBuf}; @@ -42,12 +41,6 @@ pub struct ResolvedInstallerPackage { pub acquisition: InstallerPackageAcquisition, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct InstallArtifactCachePruneResult { - pub retained_policy_key_count: usize, - pub pruned_artifact_count: usize, -} - impl ResolvedInstallerPackage { #[must_use] pub fn path(&self) -> &Path { @@ -183,36 +176,6 @@ pub fn prune_install_artifact_cache(install_root: &Path, retained_artifacts: &BT prune_cached_artifacts(&install_artifact_cache_dir(install_root), retained_artifacts) } -pub fn prune_install_artifact_cache_with_stats( - install_root: &Path, - retained_artifacts: &BTreeSet, -) -> Result { - prune_install_artifact_cache_dir_with_stats(&install_artifact_cache_dir(install_root), retained_artifacts) -} - -pub fn prune_install_artifact_cache_dir_with_stats( - artifact_cache_dir: &Path, - retained_artifacts: &BTreeSet, -) -> Result { - let pruned_artifact_count = prune_cached_artifacts(artifact_cache_dir, retained_artifacts)?; - Ok(InstallArtifactCachePruneResult { - retained_policy_key_count: retained_artifacts.len(), - pruned_artifact_count, - }) -} - -#[must_use] -pub fn retained_artifacts_for_resolved_install_cache<'a>( - package: &'a ResolvedInstallerPackage, - manifest: &InstallerManifest, -) -> Option>> { - if let Some(retained_artifacts) = &package.retained_artifacts { - Some(Cow::Borrowed(retained_artifacts)) - } else { - retained_artifacts_for_install_cache_without_index(manifest).map(Cow::Owned) - } -} - #[must_use] pub fn retained_artifacts_for_install_cache_without_index(manifest: &InstallerManifest) -> Option> { retained_artifacts_for_cache_policy_without_index( diff --git a/crates/surge-core/src/update/manager.rs b/crates/surge-core/src/update/manager.rs index 212f8e2..8457336 100644 --- a/crates/surge-core/src/update/manager.rs +++ b/crates/surge-core/src/update/manager.rs @@ -3523,97 +3523,6 @@ echo started > new-child-started assert!(remaining.is_empty(), "none retention should prune all cached artifacts"); } - #[tokio::test] - async fn test_download_and_apply_latest_full_artifact_retention_prunes_old_fulls_and_deltas() { - let tmp = tempfile::tempdir().unwrap(); - let store_root = tmp.path().join("store"); - let install_root = tmp.path().join("install"); - let app_id = "test-app"; - std::fs::create_dir_all(&store_root).unwrap(); - std::fs::create_dir_all(&install_root).unwrap(); - let app_store = app_scoped_store_root(&store_root, app_id); - - let current_app_dir = install_root.join("app"); - std::fs::create_dir_all(¤t_app_dir).unwrap(); - std::fs::write(current_app_dir.join("payload.txt"), "old payload").unwrap(); - - let rid = current_rid(); - let old_full_filename = format!("{app_id}-1.0.0-{rid}-full.tar.zst"); - let old_delta_filename = format!("{app_id}-1.0.0-{rid}-delta.tar.zst"); - let latest_full_filename = format!("{app_id}-1.1.0-{rid}-full.tar.zst"); - let latest_full_path = app_store.join(&latest_full_filename); - - let artifact_cache = install_root.join(".surge-cache").join("artifacts"); - std::fs::create_dir_all(&artifact_cache).unwrap(); - std::fs::write(artifact_cache.join(&old_full_filename), b"old full").unwrap(); - std::fs::write(artifact_cache.join(&old_delta_filename), b"old delta").unwrap(); - - let mut packer = ArchivePacker::new(3).unwrap(); - packer.add_buffer("payload.txt", b"new payload", 0o644).unwrap(); - packer.finalize_to_file(&latest_full_path).unwrap(); - - let latest_full_size = std::fs::metadata(&latest_full_path).unwrap().len() as i64; - let latest_full_sha256 = sha256_hex_file(&latest_full_path).unwrap(); - let os = current_os_label_for_tests(); - - let mut old_release = make_entry("1.0.0", "stable", &os, &rid); - old_release.full_filename = old_full_filename.clone(); - old_release.deltas = Vec::new(); - old_release.preferred_delta_id.clear(); - - let mut latest_release = make_entry("1.1.0", "stable", &os, &rid); - latest_release.full_filename = latest_full_filename.clone(); - latest_release.full_size = latest_full_size; - latest_release.full_sha256 = latest_full_sha256; - latest_release.deltas = Vec::new(); - latest_release.preferred_delta_id.clear(); - - let index = ReleaseIndex { - app_id: app_id.to_string(), - releases: vec![old_release, latest_release], - ..ReleaseIndex::default() - }; - - write_app_scoped_release_index(&store_root, app_id, &index); - - let ctx = Arc::new(Context::new()); - ctx.set_storage( - StorageProvider::Filesystem, - store_root.to_str().unwrap(), - "", - "", - "", - "", - ); - - let mut manager = UpdateManager::new(ctx, app_id, "1.0.0", "stable", install_root.to_str().unwrap()).unwrap(); - manager - .set_artifact_retention_policy(InstallArtifactCachePolicy { - retention: InstallArtifactCacheRetention::LatestFull, - keep_full_count: 1, - }) - .unwrap(); - - let info = manager.check_for_updates().await.unwrap().unwrap(); - manager - .download_and_apply(&info, None::) - .await - .unwrap(); - - assert!( - artifact_cache.join(&latest_full_filename).is_file(), - "latest full should remain in the local artifact cache" - ); - assert!( - !artifact_cache.join(&old_full_filename).exists(), - "old full should be pruned by latest_full retention" - ); - assert!( - !artifact_cache.join(&old_delta_filename).exists(), - "old delta should be pruned by latest_full retention" - ); - } - #[cfg(target_os = "linux")] #[tokio::test] async fn test_download_and_apply_full_installs_shortcuts() { diff --git a/crates/surge-core/src/update/manager/finalize.rs b/crates/surge-core/src/update/manager/finalize.rs index 5b57d67..0be7d4e 100644 --- a/crates/surge-core/src/update/manager/finalize.rs +++ b/crates/surge-core/src/update/manager/finalize.rs @@ -19,9 +19,9 @@ use crate::install::{ InstallProfile, RuntimeManifestMetadata, copy_persistent_assets, prune_version_snapshots, storage_provider_manifest_name, write_runtime_manifest, }; -use crate::installer_package::prune_install_artifact_cache_dir_with_stats; use crate::platform::fs::atomic_rename; use crate::platform::shortcuts::install_shortcuts; +use crate::releases::artifact_cache::prune_cached_artifacts; use crate::releases::manifest::decompress_release_index; use crate::releases::restore::{ retained_artifacts_for_cache_policy, retained_artifacts_for_cache_policy_without_index, @@ -247,12 +247,12 @@ where retained_artifacts_for_cache_policy_without_index(manager.artifact_retention_policy, &latest.full_filename) }; if let Some(retained_artifacts) = retained_artifacts { - match prune_install_artifact_cache_dir_with_stats(artifact_cache_dir, &retained_artifacts) { - Ok(result) if result.pruned_artifact_count == 0 => {} - Ok(result) => { + match prune_cached_artifacts(artifact_cache_dir, &retained_artifacts) { + Ok(0) => {} + Ok(pruned) => { debug!( - pruned = result.pruned_artifact_count, - retained = result.retained_policy_key_count, + pruned, + retained = retained_artifacts.len(), "Pruned stale local artifact cache entries" ); } diff --git a/crates/surge-installer-ui/src/install.rs b/crates/surge-installer-ui/src/install.rs index 7f31b42..028a68c 100644 --- a/crates/surge-installer-ui/src/install.rs +++ b/crates/surge-installer-ui/src/install.rs @@ -15,8 +15,8 @@ use surge_core::config::installer::InstallerManifest; use surge_core::config::manifest::ShortcutLocation; use surge_core::install::{self as core_install, InstallProfile, InstallProgress, InstallProgressStage}; use surge_core::installer_package::{ - InstallArtifactCachePruneResult, InstallerPackageStage, ResolveInstallerPackageOptions, ResolvedInstallerPackage, - prune_install_artifact_cache_with_stats, resolve_installer_package, retained_artifacts_for_resolved_install_cache, + InstallerPackageStage, ResolveInstallerPackageOptions, ResolvedInstallerPackage, prune_install_artifact_cache, + resolve_installer_package, retained_artifacts_for_install_cache_without_index, }; use surge_core::platform::paths::default_install_root; use surge_core::releases::restore::RestoreProgress; @@ -30,12 +30,6 @@ pub enum ProgressUpdate { type ResolvedPackage = ResolvedInstallerPackage; -enum ArtifactCachePruneOutcome { - Skipped, - Completed(InstallArtifactCachePruneResult), - Failed(String), -} - const RESOLVE_PROGRESS: f32 = 0.05; const DOWNLOAD_START_PROGRESS: f32 = 0.10; const DOWNLOAD_END_PROGRESS: f32 = 0.40; @@ -176,11 +170,7 @@ fn run_install_inner( &manifest.storage.endpoint, ); core_install::write_runtime_manifest(&install_root.join("app"), &profile, &runtime_manifest)?; - send_artifact_cache_prune_outcome( - prune_artifact_cache_for_package(&install_root, &package, manifest), - progress_tx, - ctx, - ); + prune_artifact_cache_for_package(&install_root, &package, manifest); send(progress_tx, ctx, ProgressUpdate::Progress(1.0)); send(progress_tx, ctx, ProgressUpdate::Complete(install_root)); @@ -459,65 +449,25 @@ pub fn run_headless( &manifest.storage.endpoint, ); core_install::write_runtime_manifest(&install_root.join("app"), &profile, &runtime_manifest)?; - match prune_artifact_cache_for_package(&install_root, &package, manifest) { - ArtifactCachePruneOutcome::Skipped => {} - ArtifactCachePruneOutcome::Completed(result) => { - eprintln!("{}", format_artifact_cache_prune_result(result)); - } - ArtifactCachePruneOutcome::Failed(error) => { - eprintln!("Artifact cache pruning failed: {error}"); - } - } + prune_artifact_cache_for_package(&install_root, &package, manifest); eprintln!("Installed '{}' to '{}'", manifest.app_id, install_root.display()); Ok(install_root) } -fn prune_artifact_cache_for_package( - install_root: &Path, - package: &ResolvedPackage, - manifest: &InstallerManifest, -) -> ArtifactCachePruneOutcome { - let Some(retained_artifacts) = retained_artifacts_for_resolved_install_cache(package, manifest) else { - return ArtifactCachePruneOutcome::Skipped; +fn prune_artifact_cache_for_package(install_root: &Path, package: &ResolvedPackage, manifest: &InstallerManifest) { + let owned_retained_artifacts; + let retained_artifacts = match package.retained_artifacts.as_ref() { + Some(retained_artifacts) => retained_artifacts, + None => match retained_artifacts_for_install_cache_without_index(manifest) { + Some(retained_artifacts) => { + owned_retained_artifacts = retained_artifacts; + &owned_retained_artifacts + } + None => return, + }, }; - match prune_install_artifact_cache_with_stats(install_root, retained_artifacts.as_ref()) { - Ok(result) => ArtifactCachePruneOutcome::Completed(result), - Err(e) => ArtifactCachePruneOutcome::Failed(e.to_string()), - } -} - -fn send_artifact_cache_prune_outcome( - outcome: ArtifactCachePruneOutcome, - progress_tx: &Sender, - ctx: &egui::Context, -) { - match outcome { - ArtifactCachePruneOutcome::Skipped => {} - ArtifactCachePruneOutcome::Completed(result) => { - send( - progress_tx, - ctx, - ProgressUpdate::Status(format_artifact_cache_prune_result(result)), - ); - } - ArtifactCachePruneOutcome::Failed(error) => { - send( - progress_tx, - ctx, - ProgressUpdate::Status(format!("Artifact cache pruning failed: {error}")), - ); - } - } -} - -fn format_artifact_cache_prune_result(result: InstallArtifactCachePruneResult) -> String { - format!( - "Pruned {} stale artifact cache entr{}; retained policy key count: {}.", - result.pruned_artifact_count, - if result.pruned_artifact_count == 1 { "y" } else { "ies" }, - result.retained_policy_key_count - ) + let _ = prune_install_artifact_cache(install_root, retained_artifacts); } #[cfg(test)]