Merge branch 'main' into issue-4276

This commit is contained in:
Max Romanowski 2025-09-03 16:53:13 +02:00
commit 43832e18e2
6 changed files with 113 additions and 63 deletions

View file

@ -1,10 +1,5 @@
adding-add-shortcut-ctrlandenter = Add (shortcut: ctrl+enter) adding-add-shortcut-ctrlandenter = Add (shortcut: ctrl+enter)
adding-added = Added adding-added = Added
adding-added-cards =
Added { $count ->
[one] { $count } card
*[other] { $count } cards
}
adding-discard-current-input = Discard current input? adding-discard-current-input = Discard current input?
adding-keep-editing = Keep Editing adding-keep-editing = Keep Editing
adding-edit = Edit "{ $val }" adding-edit = Edit "{ $val }"

View file

@ -300,7 +300,7 @@ class AddCards(QMainWindow):
self.addHistory(note) self.addHistory(note)
tooltip(tr.adding_added_cards(count=changes.count), period=500) tooltip(tr.importing_cards_added(count=changes.count), period=500)
av_player.stop_and_clear_queue() av_player.stop_and_clear_queue()
self._load_new_note(sticky_fields_from=note) self._load_new_note(sticky_fields_from=note)
gui_hooks.add_cards_did_add_note(note) gui_hooks.add_cards_did_add_note(note)

View file

@ -30,6 +30,12 @@ lipo -create \
-output "$APP_LAUNCHER/Contents/MacOS/launcher" -output "$APP_LAUNCHER/Contents/MacOS/launcher"
cp "$OUTPUT_DIR/uv" "$APP_LAUNCHER/Contents/MacOS/" cp "$OUTPUT_DIR/uv" "$APP_LAUNCHER/Contents/MacOS/"
# Build install_name_tool stub
clang -arch arm64 -o "$OUTPUT_DIR/stub_arm64" stub.c
clang -arch x86_64 -o "$OUTPUT_DIR/stub_x86_64" stub.c
lipo -create "$OUTPUT_DIR/stub_arm64" "$OUTPUT_DIR/stub_x86_64" -output "$APP_LAUNCHER/Contents/MacOS/install_name_tool"
rm "$OUTPUT_DIR/stub_arm64" "$OUTPUT_DIR/stub_x86_64"
# Copy support files # Copy support files
ANKI_VERSION=$(cat ../../../.version | tr -d '\n') ANKI_VERSION=$(cat ../../../.version | tr -d '\n')
sed "s/ANKI_VERSION/$ANKI_VERSION/g" Info.plist > "$APP_LAUNCHER/Contents/Info.plist" sed "s/ANKI_VERSION/$ANKI_VERSION/g" Info.plist > "$APP_LAUNCHER/Contents/Info.plist"
@ -40,7 +46,7 @@ cp ../versions.py "$APP_LAUNCHER/Contents/Resources/"
# Codesign/bundle # Codesign/bundle
if [ -z "$NODMG" ]; then if [ -z "$NODMG" ]; then
for i in "$APP_LAUNCHER/Contents/MacOS/uv" "$APP_LAUNCHER/Contents/MacOS/launcher" "$APP_LAUNCHER"; do for i in "$APP_LAUNCHER/Contents/MacOS/uv" "$APP_LAUNCHER/Contents/MacOS/install_name_tool" "$APP_LAUNCHER/Contents/MacOS/launcher" "$APP_LAUNCHER"; do
codesign --force -vvvv -o runtime -s "Developer ID Application:" \ codesign --force -vvvv -o runtime -s "Developer ID Application:" \
--entitlements entitlements.python.xml \ --entitlements entitlements.python.xml \
"$i" "$i"

6
qt/launcher/mac/stub.c Normal file
View file

@ -0,0 +1,6 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
int main(void) {
return 0;
}

View file

@ -51,6 +51,8 @@ struct State {
previous_version: Option<String>, previous_version: Option<String>,
resources_dir: std::path::PathBuf, resources_dir: std::path::PathBuf,
venv_folder: std::path::PathBuf, venv_folder: std::path::PathBuf,
/// system Python + PyQt6 library mode
system_qt: bool,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -88,9 +90,13 @@ fn main() {
} }
fn run() -> Result<()> { fn run() -> Result<()> {
let uv_install_root = dirs::data_local_dir() let uv_install_root = if let Ok(custom_root) = std::env::var("ANKI_LAUNCHER_VENV_ROOT") {
std::path::PathBuf::from(custom_root)
} else {
dirs::data_local_dir()
.context("Unable to determine data_dir")? .context("Unable to determine data_dir")?
.join("AnkiProgramFiles"); .join("AnkiProgramFiles")
};
let (exe_dir, resources_dir) = get_exe_and_resources_dirs()?; let (exe_dir, resources_dir) = get_exe_and_resources_dirs()?;
@ -113,6 +119,8 @@ fn run() -> Result<()> {
mirror_path: uv_install_root.join("mirror"), mirror_path: uv_install_root.join("mirror"),
pyproject_modified_by_user: false, // calculated later pyproject_modified_by_user: false, // calculated later
previous_version: None, previous_version: None,
system_qt: (cfg!(unix) && !cfg!(target_os = "macos"))
&& resources_dir.join("system_qt").exists(),
resources_dir, resources_dir,
venv_folder: uv_install_root.join(".venv"), venv_folder: uv_install_root.join(".venv"),
}; };
@ -193,8 +201,8 @@ fn extract_aqt_version(state: &State) -> Option<String> {
return None; return None;
} }
let output = Command::new(&state.uv_path) let output = uv_command(state)
.current_dir(&state.uv_install_root) .ok()?
.env("VIRTUAL_ENV", &state.venv_folder) .env("VIRTUAL_ENV", &state.venv_folder)
.args(["pip", "show", "aqt"]) .args(["pip", "show", "aqt"])
.output() .output()
@ -261,24 +269,11 @@ fn handle_version_install_or_update(state: &State, choice: MainMenuChoice) -> Re
None None
}; };
let have_venv = state.venv_folder.exists();
if cfg!(target_os = "macos") && !have_developer_tools() && !have_venv {
println!("If you see a pop-up about 'install_name_tool', you can cancel it, and ignore the warning below.\n");
}
// Prepare to sync the venv // Prepare to sync the venv
let mut command = Command::new(&state.uv_path); let mut command = uv_command(state)?;
command.current_dir(&state.uv_install_root);
// remove UV_* environment variables to avoid interference
for (key, _) in std::env::vars() {
if key.starts_with("UV_") || key == "VIRTUAL_ENV" {
command.env_remove(key);
}
}
if cfg!(target_os = "macos") {
// remove CONDA_PREFIX/bin from PATH to avoid conda interference // remove CONDA_PREFIX/bin from PATH to avoid conda interference
#[cfg(target_os = "macos")]
if let Ok(conda_prefix) = std::env::var("CONDA_PREFIX") { if let Ok(conda_prefix) = std::env::var("CONDA_PREFIX") {
if let Ok(current_path) = std::env::var("PATH") { if let Ok(current_path) = std::env::var("PATH") {
let conda_bin = format!("{conda_prefix}/bin"); let conda_bin = format!("{conda_prefix}/bin");
@ -290,6 +285,30 @@ fn handle_version_install_or_update(state: &State, choice: MainMenuChoice) -> Re
command.env("PATH", new_path); command.env("PATH", new_path);
} }
} }
// put our fake install_name_tool at the top of the path to override
// potential conflicts
if let Ok(current_path) = std::env::var("PATH") {
let exe_dir = std::env::current_exe()
.ok()
.and_then(|exe| exe.parent().map(|p| p.to_path_buf()));
if let Some(exe_dir) = exe_dir {
let new_path = format!("{}:{}", exe_dir.display(), current_path);
command.env("PATH", new_path);
}
}
}
// Create venv with system site packages if system Qt is enabled
if state.system_qt {
let mut venv_command = uv_command(state)?;
venv_command.args([
"venv",
"--no-managed-python",
"--system-site-packages",
"--no-config",
]);
venv_command.ensure_success()?;
}
command command
.env("UV_CACHE_DIR", &state.uv_cache_dir) .env("UV_CACHE_DIR", &state.uv_cache_dir)
@ -297,25 +316,24 @@ fn handle_version_install_or_update(state: &State, choice: MainMenuChoice) -> Re
.env( .env(
"UV_HTTP_TIMEOUT", "UV_HTTP_TIMEOUT",
std::env::var("UV_HTTP_TIMEOUT").unwrap_or_else(|_| "180".to_string()), std::env::var("UV_HTTP_TIMEOUT").unwrap_or_else(|_| "180".to_string()),
) );
.args(["sync", "--upgrade", "--managed-python", "--no-config"]);
// Add python version if .python-version file exists command.args(["sync", "--upgrade", "--no-config"]);
if !state.system_qt {
command.arg("--managed-python");
}
// Add python version if .python-version file exists (but not for system Qt)
if let Some(version) = &python_version_trimmed { if let Some(version) = &python_version_trimmed {
if !state.system_qt {
command.args(["--python", version]); command.args(["--python", version]);
} }
}
if state.no_cache_marker.exists() { if state.no_cache_marker.exists() {
command.env("UV_NO_CACHE", "1"); command.env("UV_NO_CACHE", "1");
} }
// Add mirror environment variable if enabled
if let Some((python_mirror, pypi_mirror)) = get_mirror_urls(state)? {
command
.env("UV_PYTHON_INSTALL_MIRROR", &python_mirror)
.env("UV_DEFAULT_INDEX", &pypi_mirror);
}
match command.ensure_success() { match command.ensure_success() {
Ok(_) => { Ok(_) => {
// Sync succeeded // Sync succeeded
@ -665,9 +683,8 @@ fn filter_and_normalize_versions(
fn fetch_versions(state: &State) -> Result<Vec<String>> { fn fetch_versions(state: &State) -> Result<Vec<String>> {
let versions_script = state.resources_dir.join("versions.py"); let versions_script = state.resources_dir.join("versions.py");
let mut cmd = Command::new(&state.uv_path); let mut cmd = uv_command(state)?;
cmd.current_dir(&state.uv_install_root) cmd.args(["run", "--no-project", "--no-config", "--managed-python"])
.args(["run", "--no-project", "--no-config", "--managed-python"])
.args(["--with", "pip-system-certs,requests[socks]"]); .args(["--with", "pip-system-certs,requests[socks]"]);
let python_version = read_file(&state.dist_python_version_path)?; let python_version = read_file(&state.dist_python_version_path)?;
@ -680,12 +697,6 @@ fn fetch_versions(state: &State) -> Result<Vec<String>> {
cmd.arg(&versions_script); cmd.arg(&versions_script);
// Add mirror environment variable if enabled
if let Some((python_mirror, pypi_mirror)) = get_mirror_urls(state)? {
cmd.env("UV_PYTHON_INSTALL_MIRROR", &python_mirror)
.env("UV_DEFAULT_INDEX", &pypi_mirror);
}
let output = match cmd.utf8_output() { let output = match cmd.utf8_output() {
Ok(output) => output, Ok(output) => output,
Err(e) => { Err(e) => {
@ -738,7 +749,26 @@ fn apply_version_kind(version_kind: &VersionKind, state: &State) -> Result<()> {
&format!("anki-release=={version}\",\n \"anki=={version}\",\n \"aqt=={version}"), &format!("anki-release=={version}\",\n \"anki=={version}\",\n \"aqt=={version}"),
), ),
}; };
write_file(&state.user_pyproject_path, &updated_content)?;
let final_content = if state.system_qt {
format!(
concat!(
"{}\n\n[tool.uv]\n",
"override-dependencies = [\n",
" \"pyqt6; sys_platform=='never'\",\n",
" \"pyqt6-qt6; sys_platform=='never'\",\n",
" \"pyqt6-webengine; sys_platform=='never'\",\n",
" \"pyqt6-webengine-qt6; sys_platform=='never'\",\n",
" \"pyqt6_sip; sys_platform=='never'\"\n",
"]\n"
),
updated_content
)
} else {
updated_content
};
write_file(&state.user_pyproject_path, &final_content)?;
// Update .python-version based on version kind // Update .python-version based on version kind
match version_kind { match version_kind {
@ -930,12 +960,25 @@ fn handle_uninstall(state: &State) -> Result<bool> {
Ok(true) Ok(true)
} }
fn have_developer_tools() -> bool { fn uv_command(state: &State) -> Result<Command> {
Command::new("xcode-select") let mut command = Command::new(&state.uv_path);
.args(["-p"]) command.current_dir(&state.uv_install_root);
.output()
.map(|output| output.status.success()) // remove UV_* environment variables to avoid interference
.unwrap_or(false) for (key, _) in std::env::vars() {
if key.starts_with("UV_") || key == "VIRTUAL_ENV" {
command.env_remove(key);
}
}
// Add mirror environment variable if enabled
if let Some((python_mirror, pypi_mirror)) = get_mirror_urls(state)? {
command
.env("UV_PYTHON_INSTALL_MIRROR", &python_mirror)
.env("UV_DEFAULT_INDEX", &pypi_mirror);
}
Ok(command)
} }
fn build_python_command(state: &State, args: &[String]) -> Result<Command> { fn build_python_command(state: &State, args: &[String]) -> Result<Command> {

View file

@ -62,7 +62,7 @@ pub fn prepare_for_launch_after_update(mut cmd: Command, root: &Path) -> Result<
pub fn relaunch_in_terminal() -> Result<()> { pub fn relaunch_in_terminal() -> Result<()> {
let current_exe = std::env::current_exe().context("Failed to get current executable path")?; let current_exe = std::env::current_exe().context("Failed to get current executable path")?;
Command::new("open") Command::new("open")
.args(["-a", "Terminal"]) .args(["-na", "Terminal"])
.arg(current_exe) .arg(current_exe)
.ensure_spawn()?; .ensure_spawn()?;
std::process::exit(0); std::process::exit(0);