chore: get build version at build time

This commit is contained in:
zhom
2025-05-30 07:37:40 +04:00
parent 31326c2d1f
commit 63900bd0ad
7 changed files with 189 additions and 23 deletions
+1
View File
@@ -126,6 +126,7 @@ jobs:
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REF_NAME: ${{ github.ref_name }}
with:
tagName: ${{ github.ref_name }}
releaseName: "Donut Browser ${{ github.ref_name }}"
+2
View File
@@ -94,6 +94,8 @@ jobs:
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REF_NAME: "nightly-${{ steps.commit.outputs.hash }}"
GITHUB_SHA: ${{ github.sha }}
with:
tagName: "nightly-${{ steps.commit.outputs.hash }}"
releaseName: "Donut Browser Nightly (Build ${{ steps.commit.outputs.hash }})"
+17
View File
@@ -5,5 +5,22 @@ fn main() {
println!("cargo:rustc-link-lib=framework=CoreServices");
}
// Inject build version based on environment variables set by CI
if let Ok(tag_name) = std::env::var("GITHUB_REF_NAME") {
// This is set by GitHub Actions to the tag name (e.g., "v1.0.0" or "nightly-abc123")
println!("cargo:rustc-env=BUILD_VERSION={}", tag_name);
} else if std::env::var("STABLE_RELEASE").is_ok() {
// Fallback for stable releases - use CARGO_PKG_VERSION with 'v' prefix
let version = std::env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "0.1.0".to_string());
println!("cargo:rustc-env=BUILD_VERSION=v{}", version);
} else if let Ok(commit_hash) = std::env::var("GITHUB_SHA") {
// For nightly builds, use commit hash
let short_hash = &commit_hash[0..7.min(commit_hash.len())];
println!("cargo:rustc-env=BUILD_VERSION=nightly-{}", short_hash);
} else {
// Development build fallback
println!("cargo:rustc-env=BUILD_VERSION=dev-{}", std::env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "0.1.0".to_string()));
}
tauri_build::build()
}
+118 -18
View File
@@ -51,9 +51,10 @@ impl AppAutoUpdater {
option_env!("STABLE_RELEASE").is_none()
}
/// Get current app version from Cargo.toml
/// Get current app version from build-time injection
pub fn get_current_version() -> String {
env!("CARGO_PKG_VERSION").to_string()
// Use build-time injected version instead of CARGO_PKG_VERSION
env!("BUILD_VERSION").to_string()
}
/// Check for app updates
@@ -63,37 +64,48 @@ impl AppAutoUpdater {
let current_version = Self::get_current_version();
let is_nightly = Self::is_nightly_build();
println!("Checking for updates - Current version: {current_version}, Is nightly: {is_nightly}");
println!("=== App Update Check ===");
println!("Current version: {}", current_version);
println!("Is nightly build: {}", is_nightly);
println!("STABLE_RELEASE env: {:?}", option_env!("STABLE_RELEASE"));
let releases = self.fetch_app_releases().await?;
println!("Fetched {} releases from GitHub", releases.len());
// Filter releases based on build type
let filtered_releases: Vec<&AppRelease> = if is_nightly {
// For nightly builds, look for nightly releases
releases
let nightly_releases: Vec<&AppRelease> = releases
.iter()
.filter(|release| release.tag_name.starts_with("nightly-"))
.collect()
.collect();
println!("Found {} nightly releases", nightly_releases.len());
nightly_releases
} else {
// For stable builds, look for stable releases (semver format)
releases
let stable_releases: Vec<&AppRelease> = releases
.iter()
.filter(|release| {
release.tag_name.starts_with('v') && !release.tag_name.starts_with("nightly-")
})
.collect()
.collect();
println!("Found {} stable releases", stable_releases.len());
stable_releases
};
if filtered_releases.is_empty() {
println!("No releases found for build type");
println!("No releases found for build type (nightly: {})", is_nightly);
return Ok(None);
}
// Get the latest release
let latest_release = filtered_releases[0];
println!("Latest release: {} ({})", latest_release.tag_name, latest_release.name);
// Check if we need to update
if self.should_update(&current_version, &latest_release.tag_name, is_nightly) {
println!("Update available!");
// Find the appropriate asset for current platform
if let Some(download_url) = self.get_download_url_for_platform(&latest_release.assets) {
let update_info = AppUpdateInfo {
@@ -105,8 +117,13 @@ impl AppAutoUpdater {
published_at: latest_release.published_at.clone(),
};
println!("Update info prepared: {} -> {}", update_info.current_version, update_info.new_version);
return Ok(Some(update_info));
} else {
println!("No suitable download asset found for current platform");
}
} else {
println!("No update needed");
}
Ok(None)
@@ -134,21 +151,34 @@ impl AppAutoUpdater {
/// Determine if an update should be performed
fn should_update(&self, current_version: &str, new_version: &str, is_nightly: bool) -> bool {
println!("Comparing versions: current={}, new={}, is_nightly={}", current_version, new_version, is_nightly);
if is_nightly {
// For nightly builds, always update if there's a newer nightly
// Compare the commit hashes (assuming format: nightly-<commit_hash>)
if let (Some(current_hash), Some(new_hash)) = (
current_version.strip_prefix("nightly-"),
new_version.strip_prefix("nightly-"),
) {
return new_hash != current_hash;
// Different commit hashes mean we should update
let should_update = new_hash != current_hash;
println!("Nightly comparison: current_hash={}, new_hash={}, should_update={}", current_hash, new_hash, should_update);
return should_update;
}
// If current version doesn't have nightly prefix but we're in nightly mode,
// this could be a dev build or stable build upgrading to nightly
if !current_version.starts_with("nightly-") {
println!("Upgrading from non-nightly to nightly: {}", new_version);
return true;
}
// If current version doesn't have nightly prefix, it's an upgrade from stable to nightly
!current_version.starts_with("nightly-")
} else {
// For stable builds, use semantic versioning comparison
self.is_version_newer(new_version, current_version)
let should_update = self.is_version_newer(new_version, current_version);
println!("Stable comparison: {} > {} = {}", new_version, current_version, should_update);
return should_update;
}
false
}
/// Compare semantic versions (returns true if version1 > version2)
@@ -174,24 +204,63 @@ impl AppAutoUpdater {
fn get_download_url_for_platform(&self, assets: &[AppReleaseAsset]) -> Option<String> {
let arch = if cfg!(target_arch = "aarch64") {
"aarch64"
} else {
} else if cfg!(target_arch = "x86_64") {
"x64"
} else {
"unknown"
};
// Look for macOS DMG with the appropriate architecture
println!("Looking for assets with architecture: {}", arch);
for asset in assets {
if asset.name.contains(".dmg") && asset.name.contains(arch) {
println!("Found asset: {}", asset.name);
}
// Priority 1: Look for exact architecture match in DMG
for asset in assets {
if asset.name.contains(".dmg") &&
(asset.name.contains(&format!("_{}.dmg", arch)) ||
asset.name.contains(&format!("-{}.dmg", arch)) ||
asset.name.contains(&format!("_{}_", arch)) ||
asset.name.contains(&format!("-{}-", arch))) {
println!("Found exact architecture match: {}", asset.name);
return Some(asset.browser_download_url.clone());
}
}
// Fallback: look for any macOS DMG
// Priority 2: Look for x86_64 variations if we're looking for x64
if arch == "x64" {
for asset in assets {
if asset.name.contains(".dmg") &&
(asset.name.contains("x86_64") || asset.name.contains("x86-64")) {
println!("Found x86_64 variant: {}", asset.name);
return Some(asset.browser_download_url.clone());
}
}
}
// Priority 3: Look for arm64 variations if we're looking for aarch64
if arch == "aarch64" {
for asset in assets {
if asset.name.contains(".dmg") &&
(asset.name.contains("arm64") || asset.name.contains("aarch64")) {
println!("Found arm64 variant: {}", asset.name);
return Some(asset.browser_download_url.clone());
}
}
}
// Priority 4: Fallback to any macOS DMG
for asset in assets {
if asset.name.contains(".dmg") {
if asset.name.contains(".dmg") &&
(asset.name.to_lowercase().contains("macos") ||
asset.name.to_lowercase().contains("darwin") ||
asset.name.contains(".app.tar.gz") == false) { // Exclude app.tar.gz files
println!("Found fallback DMG: {}", asset.name);
return Some(asset.browser_download_url.clone());
}
}
println!("No suitable asset found for platform");
None
}
@@ -448,6 +517,16 @@ pub fn get_app_version_info() -> Result<(String, bool), String> {
))
}
#[tauri::command]
pub async fn check_for_app_updates_manual() -> Result<Option<AppUpdateInfo>, String> {
println!("Manual app update check triggered");
let updater = AppAutoUpdater::new();
updater
.check_for_updates()
.await
.map_err(|e| format!("Failed to check for app updates: {e}"))
}
#[cfg(test)]
mod tests {
use super::*;
@@ -502,6 +581,27 @@ mod tests {
// Upgrade from stable to nightly
assert!(updater.should_update("v1.0.0", "nightly-abc123", true));
// Upgrade from dev to nightly
assert!(updater.should_update("dev-0.1.0", "nightly-abc123", true));
}
#[test]
fn test_should_update_edge_cases() {
let updater = AppAutoUpdater::new();
// Test with different nightly formats
assert!(updater.should_update("nightly-abc123", "nightly-def456", true));
assert!(!updater.should_update("nightly-abc123", "nightly-abc123", true));
// Test stable version edge cases
assert!(updater.should_update("v0.9.9", "v1.0.0", false));
assert!(!updater.should_update("v1.0.0", "v0.9.9", false));
assert!(!updater.should_update("v1.0.0", "v1.0.0", false));
// Test version without 'v' prefix
assert!(updater.should_update("0.9.9", "v1.0.0", false));
assert!(updater.should_update("v0.9.9", "1.0.0", false));
}
#[test]
+9 -3
View File
@@ -54,7 +54,7 @@ use auto_updater::{
};
use app_auto_updater::{
check_for_app_updates, download_and_install_app_update, get_app_version_info,
check_for_app_updates, check_for_app_updates_manual, download_and_install_app_update, get_app_version_info,
};
#[tauri::command]
@@ -183,6 +183,7 @@ pub fn run() {
// Add a small delay to ensure the app is fully loaded
tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
println!("Starting app update check at startup...");
let updater = app_auto_updater::AppAutoUpdater::new();
match updater.check_for_updates().await {
Ok(Some(update_info)) => {
@@ -191,13 +192,17 @@ pub fn run() {
update_info.current_version, update_info.new_version
);
// Emit update available event to the frontend
let _ = app_handle_update.emit("app-update-available", &update_info);
if let Err(e) = app_handle_update.emit("app-update-available", &update_info) {
eprintln!("Failed to emit app update event: {}", e);
} else {
println!("App update event emitted successfully");
}
}
Ok(None) => {
println!("No app updates available");
}
Err(e) => {
eprintln!("Failed to check for app updates: {e}");
eprintln!("Failed to check for app updates: {}", e);
}
}
});
@@ -255,6 +260,7 @@ pub fn run() {
remove_auto_update_download,
is_auto_update_download,
check_for_app_updates,
check_for_app_updates_manual,
download_and_install_app_update,
get_app_version_info,
])
+1
View File
@@ -56,6 +56,7 @@ export default function Home() {
// App auto-update functionality
const appUpdateNotifications = useAppUpdateNotifications();
const { checkForAppUpdatesManual } = appUpdateNotifications;
// Ensure we're on the client side to prevent hydration mismatches
useEffect(() => {
+41 -2
View File
@@ -13,6 +13,7 @@ export function useAppUpdateNotifications() {
const [isUpdating, setIsUpdating] = useState(false);
const [updateProgress, setUpdateProgress] = useState<string>("");
const [isClient, setIsClient] = useState(false);
const [dismissedVersion, setDismissedVersion] = useState<string | null>(null);
// Ensure we're on the client side to prevent hydration mismatches
useEffect(() => {
@@ -26,10 +27,33 @@ export function useAppUpdateNotifications() {
const update = await invoke<AppUpdateInfo | null>(
"check_for_app_updates",
);
setUpdateInfo(update);
// Don't show update if this version was already dismissed
if (update && update.new_version !== dismissedVersion) {
setUpdateInfo(update);
} else if (update) {
console.log("Update available but dismissed:", update.new_version);
}
} catch (error) {
console.error("Failed to check for app updates:", error);
}
}, [isClient, dismissedVersion]);
const checkForAppUpdatesManual = useCallback(async () => {
if (!isClient) return;
try {
console.log("Triggering manual app update check...");
const update = await invoke<AppUpdateInfo | null>(
"check_for_app_updates_manual",
);
console.log("Manual check result:", update);
// Always show manual check results, even if previously dismissed
setUpdateInfo(update);
} catch (error) {
console.error("Failed to manually check for app updates:", error);
}
}, [isClient]);
const handleAppUpdate = useCallback(async (appUpdateInfo: AppUpdateInfo) => {
@@ -56,9 +80,15 @@ export function useAppUpdateNotifications() {
const dismissAppUpdate = useCallback(() => {
if (!isClient) return;
// Remember the dismissed version so we don't show it again
if (updateInfo) {
setDismissedVersion(updateInfo.new_version);
console.log("Dismissed app update version:", updateInfo.new_version);
}
setUpdateInfo(null);
toast.dismiss("app-update");
}, [isClient]);
}, [isClient, updateInfo]);
// Listen for app update availability
useEffect(() => {
@@ -116,10 +146,19 @@ export function useAppUpdateNotifications() {
isClient,
]);
// Check for app updates on startup
useEffect(() => {
if (!isClient) return;
// Check for updates immediately on startup
void checkForAppUpdates();
}, [isClient, checkForAppUpdates]);
return {
updateInfo,
isUpdating,
checkForAppUpdates,
checkForAppUpdatesManual,
dismissAppUpdate,
};
}