mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-21 13:08:29 +00:00
- `build.kind = "efi"` — UEFI PE32+ executable support. Links with `-shared -nostdlib -Wl,-dll -Wl,--subsystem,10`, output via `efi_path()` with `.efi` extension, rejected by `dcr run`.
- `build.kind = "elf"` — bare-metal ELF executable support. Uses `elf_path()` output path, rejected by `dcr run`, no extra linker flags.
Fixed:
- `dcr run` with `--target` or `build.target` now correctly finds the binary in the arch-specific target directory.
- `dcr run` checks profile-specific `build.{profile}.kind` before rejecting library builds (previously only checked `build.kind`).
- `dcr clean` now reads `build.target` from `dcr.toml` when no `--target` flag is given.
- Linux: target directory is now consistent — `target/<arch>-unknown-linux-gnu/<profile>/` (no stale `target/<profile>/`).
- `dcr.lock` is now populated with all resolved dependencies (registry, path, git) instead of being always empty.
- Git dependencies are now properly recognized and recorded in `dcr.lock`.
- `is_registry_dep` correctly identifies version-based registry dependency tables (`{ version = "..." }`) and rejects tables with unknown keys.
- `dcr test` accepts `--debug`/`--release` flags (defaults to `debug` profile instead of always `release`).
- Build fingerprint cache (`build_cache_path`) now includes `target_dir` — different targets no longer share a single cache entry.
- `object_path` no longer hardcodes the `src/` prefix — works correctly with custom `build.roots` (e.g. `roots = ["lib"]`).
- `dcr update` now generates `.exe` candidates for all Windows targets, not just `x86_64-pc-windows-msvc`.
- Compiler existence is verified before starting the build (clear error if compiler is not found in PATH).
- Consolidated duplicate `OUTPUT_MUTEX` definitions — MSVC backend now uses the shared mutex from `common.rs`.
- Registry cache root directory is created if missing (prevents cryptic errors when `~/.dcr/` does not exist).
191 lines
5.2 KiB
Rust
191 lines
5.2 KiB
Rust
use crate::utils::log::{error, warn};
|
|
use reqwest::blocking::Client;
|
|
use serde::Deserialize;
|
|
use std::fs;
|
|
use std::path::{Path, PathBuf};
|
|
#[cfg(target_os = "linux")]
|
|
use std::process::Command;
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
|
const LATEST_RELEASE_URL: &str = "https://api.github.com/repos/dexoron/dcr/releases/latest";
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct Release {
|
|
tag_name: String,
|
|
assets: Vec<ReleaseAsset>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct ReleaseAsset {
|
|
name: String,
|
|
browser_download_url: String,
|
|
}
|
|
|
|
pub fn flag_update(args: &[String]) -> i32 {
|
|
if !args.is_empty() {
|
|
warn("Command does not support additional arguments");
|
|
return 1;
|
|
}
|
|
|
|
let current_exe = match std::env::current_exe() {
|
|
Ok(path) => path,
|
|
Err(_) => {
|
|
error("Failed to resolve current binary path");
|
|
return 1;
|
|
}
|
|
};
|
|
|
|
if let Some(package_name) = pacman_owned_package(¤t_exe) {
|
|
warn("This dcr binary is managed by pacman/AUR");
|
|
println!(
|
|
"Update via package manager: yay/paru -Syu {package_name} or sudo pacman -Syu {package_name}"
|
|
);
|
|
return 0;
|
|
}
|
|
|
|
let current_version = env!("CARGO_PKG_VERSION");
|
|
let target = env!("DCR_TARGET");
|
|
let client = match Client::builder().user_agent("dcr-updater").build() {
|
|
Ok(client) => client,
|
|
Err(_) => {
|
|
error("Failed to initialize HTTP client");
|
|
return 1;
|
|
}
|
|
};
|
|
|
|
let release = match fetch_latest_release(&client) {
|
|
Ok(release) => release,
|
|
Err(err) => {
|
|
error(&format!("Failed to check for updates: {err}"));
|
|
return 1;
|
|
}
|
|
};
|
|
|
|
let latest_version = release.tag_name.trim_start_matches('v');
|
|
if latest_version == current_version {
|
|
println!("Latest version is already installed: {current_version}");
|
|
return 0;
|
|
}
|
|
|
|
let candidate_names = asset_candidates(target);
|
|
let Some(asset) = release
|
|
.assets
|
|
.iter()
|
|
.find(|asset| candidate_names.iter().any(|name| name == &asset.name))
|
|
else {
|
|
error(&format!("Binary for target {target} not found"));
|
|
return 1;
|
|
};
|
|
|
|
let bytes = match download_asset(&client, &asset.browser_download_url) {
|
|
Ok(bytes) => bytes,
|
|
Err(err) => {
|
|
error(&format!("Failed to download update: {err}"));
|
|
return 1;
|
|
}
|
|
};
|
|
|
|
let temp_path = temp_binary_path(¤t_exe);
|
|
|
|
if fs::write(&temp_path, &bytes).is_err() {
|
|
error("Failed to write temporary binary");
|
|
return 1;
|
|
}
|
|
set_executable_permissions(&temp_path);
|
|
|
|
if self_replace::self_replace(&temp_path).is_err() {
|
|
let _ = fs::remove_file(&temp_path);
|
|
error("Failed to replace current binary");
|
|
return 1;
|
|
}
|
|
|
|
let _ = fs::remove_file(&temp_path);
|
|
println!("Update completed: {current_version} -> {latest_version}");
|
|
0
|
|
}
|
|
|
|
fn fetch_latest_release(client: &Client) -> Result<Release, String> {
|
|
let response = client
|
|
.get(LATEST_RELEASE_URL)
|
|
.send()
|
|
.map_err(|_| "GitHub API request failed".to_string())?;
|
|
|
|
if !response.status().is_success() {
|
|
return Err(format!("GitHub API returned status {}", response.status()));
|
|
}
|
|
|
|
response
|
|
.json::<Release>()
|
|
.map_err(|_| "GitHub API response has an unexpected format".to_string())
|
|
}
|
|
|
|
fn download_asset(client: &Client, url: &str) -> Result<Vec<u8>, String> {
|
|
let response = client
|
|
.get(url)
|
|
.send()
|
|
.map_err(|_| "Download request failed".to_string())?;
|
|
|
|
if !response.status().is_success() {
|
|
return Err(format!("Download returned status {}", response.status()));
|
|
}
|
|
|
|
response
|
|
.bytes()
|
|
.map(|bytes| bytes.to_vec())
|
|
.map_err(|_| "Failed to read downloaded data".to_string())
|
|
}
|
|
|
|
fn asset_candidates(target: &str) -> Vec<String> {
|
|
let mut names = vec![format!("dcr-{target}")];
|
|
|
|
if target.contains("-windows-") || target.ends_with("-windows") {
|
|
names.push(format!("dcr-{target}.exe"));
|
|
}
|
|
|
|
names
|
|
}
|
|
|
|
fn temp_binary_path(current_exe: &Path) -> PathBuf {
|
|
let stamp = SystemTime::now()
|
|
.duration_since(UNIX_EPOCH)
|
|
.map(|value| value.as_millis())
|
|
.unwrap_or(0);
|
|
let mut extension = format!("new-{stamp}");
|
|
if cfg!(windows) {
|
|
extension.push_str(".exe");
|
|
}
|
|
current_exe.with_extension(extension)
|
|
}
|
|
|
|
fn set_executable_permissions(_path: &Path) {
|
|
#[cfg(unix)]
|
|
{
|
|
use std::os::unix::fs::PermissionsExt;
|
|
if let Ok(meta) = fs::metadata(_path) {
|
|
let mut perms = meta.permissions();
|
|
perms.set_mode(0o755);
|
|
let _ = fs::set_permissions(_path, perms);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
fn pacman_owned_package(path: &Path) -> Option<String> {
|
|
let output = Command::new("pacman").arg("-Qoq").arg(path).output().ok()?;
|
|
if !output.status.success() {
|
|
return None;
|
|
}
|
|
|
|
let package_name = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
|
if package_name.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
Some(package_name)
|
|
}
|
|
|
|
#[cfg(not(target_os = "linux"))]
|
|
fn pacman_owned_package(_path: &Path) -> Option<String> {
|
|
None
|
|
}
|