mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Launcher tweaks
- Handle beta/rc tags in .version when launching Anki - Update pyproject.toml/.python_version if distributed version newer - Support prerelease marker to opt in to betas - Check for updates when using uv sync - Avoid system Python by default, as it can cause breakages (e.g. ARM Python installed on Windows)
This commit is contained in:
parent
4abc0eb8b8
commit
cd71931506
6 changed files with 67 additions and 38 deletions
|
@ -309,12 +309,17 @@ def int_version() -> int:
|
||||||
"""Anki's version as an integer in the form YYMMPP, e.g. 230900.
|
"""Anki's version as an integer in the form YYMMPP, e.g. 230900.
|
||||||
(year, month, patch).
|
(year, month, patch).
|
||||||
In 2.1.x releases, this was just the last number."""
|
In 2.1.x releases, this was just the last number."""
|
||||||
|
import re
|
||||||
|
|
||||||
from anki.buildinfo import version
|
from anki.buildinfo import version
|
||||||
|
|
||||||
|
# Strip non-numeric characters (handles beta/rc suffixes like '25.02b1' or 'rc3')
|
||||||
|
numeric_version = re.sub(r"[^0-9.]", "", version)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
[year, month, patch] = version.split(".")
|
[year, month, patch] = numeric_version.split(".")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
[year, month] = version.split(".")
|
[year, month] = numeric_version.split(".")
|
||||||
patch = "0"
|
patch = "0"
|
||||||
|
|
||||||
year_num = int(year)
|
year_num = int(year)
|
||||||
|
|
|
@ -36,10 +36,10 @@ class CustomBuildHook(BuildHookInterface):
|
||||||
def _set_anki_dependency(self, version: str, build_data: Dict[str, Any]) -> None:
|
def _set_anki_dependency(self, version: str, build_data: Dict[str, Any]) -> None:
|
||||||
# Get current dependencies and replace 'anki' with exact version
|
# Get current dependencies and replace 'anki' with exact version
|
||||||
dependencies = build_data.setdefault("dependencies", [])
|
dependencies = build_data.setdefault("dependencies", [])
|
||||||
|
|
||||||
# Remove any existing anki dependency
|
# Remove any existing anki dependency
|
||||||
dependencies[:] = [dep for dep in dependencies if not dep.startswith("anki")]
|
dependencies[:] = [dep for dep in dependencies if not dep.startswith("anki")]
|
||||||
|
|
||||||
# Handle version detection
|
# Handle version detection
|
||||||
actual_version = version
|
actual_version = version
|
||||||
if version == "standard":
|
if version == "standard":
|
||||||
|
@ -48,7 +48,7 @@ class CustomBuildHook(BuildHookInterface):
|
||||||
version_file = project_root / ".version"
|
version_file = project_root / ".version"
|
||||||
if version_file.exists():
|
if version_file.exists():
|
||||||
actual_version = version_file.read_text().strip()
|
actual_version = version_file.read_text().strip()
|
||||||
|
|
||||||
# Only add exact version for real releases, not editable installs
|
# Only add exact version for real releases, not editable installs
|
||||||
if actual_version != "editable":
|
if actual_version != "editable":
|
||||||
dependencies.append(f"anki=={actual_version}")
|
dependencies.append(f"anki=={actual_version}")
|
||||||
|
|
|
@ -8,6 +8,7 @@ APP_LAUNCHER="$OUTPUT_DIR/Anki.app"
|
||||||
rm -rf "$APP_LAUNCHER"
|
rm -rf "$APP_LAUNCHER"
|
||||||
|
|
||||||
# Build binaries for both architectures
|
# Build binaries for both architectures
|
||||||
|
rustup target add aarch64-apple-darwin x86_64-apple-darwin
|
||||||
cargo build -p launcher --release --target aarch64-apple-darwin
|
cargo build -p launcher --release --target aarch64-apple-darwin
|
||||||
cargo build -p launcher --release --target x86_64-apple-darwin
|
cargo build -p launcher --release --target x86_64-apple-darwin
|
||||||
(cd ../../.. && ./ninja launcher:uv_universal)
|
(cd ../../.. && ./ninja launcher:uv_universal)
|
||||||
|
|
|
@ -5,18 +5,4 @@ description = "UV-based launcher for Anki."
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anki-release",
|
"anki-release",
|
||||||
# so we can use testpypi
|
|
||||||
"anki",
|
|
||||||
"aqt",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.uv.sources]
|
|
||||||
anki-release = { index = "testpypi" }
|
|
||||||
anki = { index = "testpypi" }
|
|
||||||
aqt = { index = "testpypi" }
|
|
||||||
|
|
||||||
[[tool.uv.index]]
|
|
||||||
name = "testpypi"
|
|
||||||
url = "https://test.pypi.org/simple/"
|
|
||||||
publish-url = "https://test.pypi.org/legacy/"
|
|
||||||
explicit = true
|
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
use std::io::stdin;
|
use std::io::stdin;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use anki_io::copy_file;
|
use anki_io::copy_if_newer;
|
||||||
use anki_io::create_dir_all;
|
use anki_io::create_dir_all;
|
||||||
use anki_io::metadata;
|
use anki_io::modified_time;
|
||||||
use anki_io::remove_file;
|
use anki_io::remove_file;
|
||||||
use anki_io::write_file;
|
use anki_io::write_file;
|
||||||
use anki_process::CommandExt;
|
use anki_process::CommandExt;
|
||||||
|
@ -51,6 +51,7 @@ fn run() -> Result<()> {
|
||||||
.join("AnkiProgramFiles");
|
.join("AnkiProgramFiles");
|
||||||
|
|
||||||
let sync_complete_marker = uv_install_root.join(".sync_complete");
|
let sync_complete_marker = uv_install_root.join(".sync_complete");
|
||||||
|
let prerelease_marker = uv_install_root.join("prerelease");
|
||||||
let (exe_dir, resources_dir) = get_exe_and_resources_dirs()?;
|
let (exe_dir, resources_dir) = get_exe_and_resources_dirs()?;
|
||||||
let dist_pyproject_path = resources_dir.join("pyproject.toml");
|
let dist_pyproject_path = resources_dir.join("pyproject.toml");
|
||||||
let user_pyproject_path = uv_install_root.join("pyproject.toml");
|
let user_pyproject_path = uv_install_root.join("pyproject.toml");
|
||||||
|
@ -59,14 +60,15 @@ fn run() -> Result<()> {
|
||||||
let uv_lock_path = uv_install_root.join("uv.lock");
|
let uv_lock_path = uv_install_root.join("uv.lock");
|
||||||
let uv_path: std::path::PathBuf = exe_dir.join(get_uv_binary_name());
|
let uv_path: std::path::PathBuf = exe_dir.join(get_uv_binary_name());
|
||||||
|
|
||||||
|
// Create install directory and copy project files in
|
||||||
|
create_dir_all(&uv_install_root)?;
|
||||||
|
copy_if_newer(&dist_pyproject_path, &user_pyproject_path)?;
|
||||||
|
copy_if_newer(&dist_python_version_path, &user_python_version_path)?;
|
||||||
|
|
||||||
let pyproject_has_changed =
|
let pyproject_has_changed =
|
||||||
!user_pyproject_path.exists() || !sync_complete_marker.exists() || {
|
!user_pyproject_path.exists() || !sync_complete_marker.exists() || {
|
||||||
let pyproject_toml_time = metadata(&user_pyproject_path)?
|
let pyproject_toml_time = modified_time(&user_pyproject_path)?;
|
||||||
.modified()
|
let sync_complete_time = modified_time(&sync_complete_marker)?;
|
||||||
.context("Failed to get pyproject.toml modified time")?;
|
|
||||||
let sync_complete_time = metadata(&sync_complete_marker)?
|
|
||||||
.modified()
|
|
||||||
.context("Failed to get sync marker modified time")?;
|
|
||||||
Ok::<bool, anyhow::Error>(pyproject_toml_time > sync_complete_time)
|
Ok::<bool, anyhow::Error>(pyproject_toml_time > sync_complete_time)
|
||||||
}
|
}
|
||||||
.unwrap_or(true);
|
.unwrap_or(true);
|
||||||
|
@ -81,22 +83,21 @@ fn run() -> Result<()> {
|
||||||
// we'll need to launch uv; reinvoke ourselves in a terminal so the user can see
|
// we'll need to launch uv; reinvoke ourselves in a terminal so the user can see
|
||||||
handle_terminal_launch()?;
|
handle_terminal_launch()?;
|
||||||
|
|
||||||
// Create install directory and copy project files in
|
|
||||||
create_dir_all(&uv_install_root)?;
|
|
||||||
if !user_pyproject_path.exists() {
|
|
||||||
copy_file(&dist_pyproject_path, &user_pyproject_path)?;
|
|
||||||
copy_file(&dist_python_version_path, &user_python_version_path)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove sync marker before attempting sync
|
// Remove sync marker before attempting sync
|
||||||
let _ = remove_file(&sync_complete_marker);
|
let _ = remove_file(&sync_complete_marker);
|
||||||
|
|
||||||
// Sync the venv
|
// Sync the venv
|
||||||
if let Err(e) = Command::new(&uv_path)
|
let mut command = Command::new(&uv_path);
|
||||||
|
command
|
||||||
.current_dir(&uv_install_root)
|
.current_dir(&uv_install_root)
|
||||||
.args(["sync", "--refresh"])
|
.args(["sync", "--upgrade", "--managed-python"]);
|
||||||
.ensure_success()
|
|
||||||
{
|
// Set UV_PRERELEASE=allow if prerelease file exists
|
||||||
|
if prerelease_marker.exists() {
|
||||||
|
command.env("UV_PRERELEASE", "allow");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = command.ensure_success() {
|
||||||
// If sync fails due to things like a missing wheel on pypi,
|
// If sync fails due to things like a missing wheel on pypi,
|
||||||
// we need to remove the lockfile or uv will cache the bad result.
|
// we need to remove the lockfile or uv will cache the bad result.
|
||||||
let _ = remove_file(&uv_lock_path);
|
let _ = remove_file(&uv_lock_path);
|
||||||
|
|
|
@ -152,6 +152,34 @@ pub fn copy_file(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<u64> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Copy a file from src to dst if dst doesn't exist or if src is newer than
|
||||||
|
/// dst. Preserves the modification time from the source file.
|
||||||
|
pub fn copy_if_newer(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<bool> {
|
||||||
|
let src = src.as_ref();
|
||||||
|
let dst = dst.as_ref();
|
||||||
|
|
||||||
|
let should_copy = if !dst.exists() {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let src_time = modified_time(src)?;
|
||||||
|
let dst_time = modified_time(dst)?;
|
||||||
|
src_time > dst_time
|
||||||
|
};
|
||||||
|
|
||||||
|
if should_copy {
|
||||||
|
copy_file(src, dst)?;
|
||||||
|
|
||||||
|
// Preserve the modification time from the source file
|
||||||
|
let src_mtime = modified_time(src)?;
|
||||||
|
let times = FileTimes::new().set_modified(src_mtime);
|
||||||
|
set_file_times(dst, times)?;
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Like [read_file], but skips the section that is potentially locked by
|
/// Like [read_file], but skips the section that is potentially locked by
|
||||||
/// SQLite.
|
/// SQLite.
|
||||||
pub fn read_locked_db_file(path: impl AsRef<Path>) -> Result<Vec<u8>> {
|
pub fn read_locked_db_file(path: impl AsRef<Path>) -> Result<Vec<u8>> {
|
||||||
|
@ -188,6 +216,14 @@ pub fn metadata(path: impl AsRef<Path>) -> Result<std::fs::Metadata> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the modification time of a file.
|
||||||
|
pub fn modified_time(path: impl AsRef<Path>) -> Result<std::time::SystemTime> {
|
||||||
|
metadata(&path)?.modified().context(FileIoSnafu {
|
||||||
|
path: path.as_ref(),
|
||||||
|
op: FileOp::Metadata,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new_tempfile() -> Result<NamedTempFile> {
|
pub fn new_tempfile() -> Result<NamedTempFile> {
|
||||||
NamedTempFile::new().context(FileIoSnafu {
|
NamedTempFile::new().context(FileIoSnafu {
|
||||||
path: std::env::temp_dir(),
|
path: std::env::temp_dir(),
|
||||||
|
|
Loading…
Reference in a new issue