mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Add some helpers to allow add-ons to install packages into the venv
While something we probably don't want to encourage much of, this may enable some previously-unshared add-ons. https://forums.ankiweb.net/t/bundling-numpy-in-an-add-on/62669/5 The 'uv add' command is transaction, so if an add-on tries to inject incompatible dependencies into the environment, the venv will be left as-is. And each Anki upgrade/downgrade resets the requirements, so the requested packages shouldn't cause errors down the line. Sample add-on: import subprocess from aqt import mw from aqt.operations import QueryOp from aqt.qt import QAction from aqt.utils import showInfo def ensure_spacy(col): print("trying to import spacy") try: import spacy print("successful import") return except Exception as e: print("error importing:", e) print("attempting add") try: from aqt.package import add_python_requirements as add except Exception as e: raise Exception(f"package unavailable, can't install: {e}") # be explicit about version, or Anki beta users will get # a beta wheel that may break (success, output) = add(["spacy==3.8.7", "https://github.com/explosion/spacy-models/releases/download/ko_core_news_sm-3.8.0/ko_core_news_sm-3.8.0-py3-none-any.whl"]) if not success: raise Exception(f"adding failed: {output}") print("success") # alterantively: # from aqt.package import venv_binary # subprocess.run([venv_binary("spacy"), "download", "ko_core_news_sm"], check=True) # print("model added") # large packages will freeze for a while on first import on macOS import spacy print("spacy import successful") def activate_spacy(): def on_success(res): mw.progress.single_shot(1000, lambda: showInfo("Spacy installed")) QueryOp(parent=mw, op=ensure_spacy, success=on_success).with_progress("Installing spacy...").run_in_background() action = QAction("Activate Spacy", mw) action.triggered.connect(activate_spacy) mw.form.menuTools.addAction(action)
This commit is contained in:
parent
e81a7e8b1a
commit
bb1b289690
6 changed files with 215 additions and 113 deletions
|
@ -1309,7 +1309,7 @@ title="{}" {}>{}</button>""".format(
|
||||||
if not askUser(tr.qt_misc_open_anki_launcher()):
|
if not askUser(tr.qt_misc_open_anki_launcher()):
|
||||||
return
|
return
|
||||||
|
|
||||||
from aqt.update import update_and_restart
|
from aqt.package import update_and_restart
|
||||||
|
|
||||||
update_and_restart()
|
update_and_restart()
|
||||||
|
|
||||||
|
@ -1394,7 +1394,7 @@ title="{}" {}>{}</button>""".format(
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def setupMenus(self) -> None:
|
def setupMenus(self) -> None:
|
||||||
from aqt.update import have_launcher
|
from aqt.package import launcher_executable
|
||||||
|
|
||||||
m = self.form
|
m = self.form
|
||||||
|
|
||||||
|
@ -1426,7 +1426,7 @@ title="{}" {}>{}</button>""".format(
|
||||||
qconnect(m.actionEmptyCards.triggered, self.onEmptyCards)
|
qconnect(m.actionEmptyCards.triggered, self.onEmptyCards)
|
||||||
qconnect(m.actionNoteTypes.triggered, self.onNoteTypes)
|
qconnect(m.actionNoteTypes.triggered, self.onNoteTypes)
|
||||||
qconnect(m.action_upgrade_downgrade.triggered, self.on_upgrade_downgrade)
|
qconnect(m.action_upgrade_downgrade.triggered, self.on_upgrade_downgrade)
|
||||||
if not have_launcher():
|
if not launcher_executable():
|
||||||
m.action_upgrade_downgrade.setVisible(False)
|
m.action_upgrade_downgrade.setVisible(False)
|
||||||
qconnect(m.actionPreferences.triggered, self.onPrefs)
|
qconnect(m.actionPreferences.triggered, self.onPrefs)
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,13 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from anki.utils import is_mac
|
from anki.utils import is_mac, is_win
|
||||||
|
|
||||||
|
|
||||||
# ruff: noqa: F401
|
# ruff: noqa: F401
|
||||||
|
@ -65,3 +68,101 @@ def first_run_setup() -> None:
|
||||||
# Wait for both commands to complete
|
# Wait for both commands to complete
|
||||||
for proc in processes:
|
for proc in processes:
|
||||||
proc.wait()
|
proc.wait()
|
||||||
|
|
||||||
|
|
||||||
|
def uv_binary() -> str | None:
|
||||||
|
"""Return the path to the uv binary."""
|
||||||
|
return os.environ.get("ANKI_LAUNCHER_UV")
|
||||||
|
|
||||||
|
|
||||||
|
def launcher_root() -> str | None:
|
||||||
|
"""Return the path to the launcher root directory (AnkiProgramFiles)."""
|
||||||
|
return os.environ.get("UV_PROJECT")
|
||||||
|
|
||||||
|
|
||||||
|
def venv_binary(cmd: str) -> str | None:
|
||||||
|
"""Return the path to a binary in the launcher's venv."""
|
||||||
|
root = launcher_root()
|
||||||
|
if not root:
|
||||||
|
return None
|
||||||
|
|
||||||
|
root_path = Path(root)
|
||||||
|
if is_win:
|
||||||
|
binary_path = root_path / ".venv" / "Scripts" / cmd
|
||||||
|
else:
|
||||||
|
binary_path = root_path / ".venv" / "bin" / cmd
|
||||||
|
|
||||||
|
return str(binary_path)
|
||||||
|
|
||||||
|
|
||||||
|
def add_python_requirements(reqs: list[str]) -> tuple[bool, str]:
|
||||||
|
"""Add Python requirements to the launcher venv using uv add.
|
||||||
|
|
||||||
|
Returns (success, output)"""
|
||||||
|
|
||||||
|
binary = uv_binary()
|
||||||
|
if not binary:
|
||||||
|
return (False, "Not in packaged build.")
|
||||||
|
|
||||||
|
uv_cmd = [binary, "add"] + reqs
|
||||||
|
result = subprocess.run(uv_cmd, capture_output=True, text=True, check=False)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
root = launcher_root()
|
||||||
|
if root:
|
||||||
|
sync_marker = Path(root) / ".sync_complete"
|
||||||
|
sync_marker.touch()
|
||||||
|
|
||||||
|
return (True, result.stdout)
|
||||||
|
else:
|
||||||
|
return (False, result.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
def launcher_executable() -> str | None:
|
||||||
|
"""Return the path to the Anki launcher executable."""
|
||||||
|
return os.getenv("ANKI_LAUNCHER")
|
||||||
|
|
||||||
|
|
||||||
|
def trigger_launcher_run() -> None:
|
||||||
|
"""Bump the mtime on pyproject.toml in the local data directory to trigger an update on next run."""
|
||||||
|
try:
|
||||||
|
root = launcher_root()
|
||||||
|
if not root:
|
||||||
|
return
|
||||||
|
|
||||||
|
pyproject_path = Path(root) / "pyproject.toml"
|
||||||
|
|
||||||
|
if pyproject_path.exists():
|
||||||
|
# Touch the file to update its mtime
|
||||||
|
pyproject_path.touch()
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
|
||||||
|
def update_and_restart() -> None:
|
||||||
|
"""Update and restart Anki using the launcher."""
|
||||||
|
from aqt import mw
|
||||||
|
|
||||||
|
launcher = launcher_executable()
|
||||||
|
assert launcher
|
||||||
|
|
||||||
|
trigger_launcher_run()
|
||||||
|
|
||||||
|
with contextlib.suppress(ResourceWarning):
|
||||||
|
env = os.environ.copy()
|
||||||
|
creationflags = 0
|
||||||
|
if sys.platform == "win32":
|
||||||
|
creationflags = (
|
||||||
|
subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS
|
||||||
|
)
|
||||||
|
subprocess.Popen(
|
||||||
|
[launcher],
|
||||||
|
start_new_session=True,
|
||||||
|
stdin=subprocess.DEVNULL,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
env=env,
|
||||||
|
creationflags=creationflags,
|
||||||
|
)
|
||||||
|
|
||||||
|
mw.app.quit()
|
||||||
|
|
|
@ -3,16 +3,17 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import contextlib
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
from anki.buildinfo import buildhash
|
from anki.buildinfo import buildhash
|
||||||
from anki.collection import CheckForUpdateResponse, Collection
|
from anki.collection import CheckForUpdateResponse, Collection
|
||||||
from anki.utils import dev_mode, int_time, int_version, is_mac, is_win, plat_desc
|
from anki.utils import dev_mode, int_time, int_version, plat_desc
|
||||||
from aqt.operations import QueryOp
|
from aqt.operations import QueryOp
|
||||||
|
from aqt.package import (
|
||||||
|
launcher_executable as _launcher_executable,
|
||||||
|
)
|
||||||
|
from aqt.package import (
|
||||||
|
update_and_restart as _update_and_restart,
|
||||||
|
)
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.utils import openLink, show_warning, showText, tr
|
from aqt.utils import openLink, show_warning, showText, tr
|
||||||
|
|
||||||
|
@ -84,67 +85,7 @@ def prompt_to_update(mw: aqt.AnkiQt, ver: str) -> None:
|
||||||
# ignore this update
|
# ignore this update
|
||||||
mw.pm.meta["suppressUpdate"] = ver
|
mw.pm.meta["suppressUpdate"] = ver
|
||||||
elif ret == QMessageBox.StandardButton.Yes:
|
elif ret == QMessageBox.StandardButton.Yes:
|
||||||
if have_launcher():
|
if _launcher_executable():
|
||||||
update_and_restart()
|
_update_and_restart()
|
||||||
else:
|
else:
|
||||||
openLink(aqt.appWebsiteDownloadSection)
|
openLink(aqt.appWebsiteDownloadSection)
|
||||||
|
|
||||||
|
|
||||||
def _anki_launcher_path() -> str | None:
|
|
||||||
return os.getenv("ANKI_LAUNCHER")
|
|
||||||
|
|
||||||
|
|
||||||
def have_launcher() -> bool:
|
|
||||||
return _anki_launcher_path() is not None
|
|
||||||
|
|
||||||
|
|
||||||
def update_and_restart() -> None:
|
|
||||||
from aqt import mw
|
|
||||||
|
|
||||||
launcher = _anki_launcher_path()
|
|
||||||
assert launcher
|
|
||||||
|
|
||||||
_trigger_launcher_run()
|
|
||||||
|
|
||||||
with contextlib.suppress(ResourceWarning):
|
|
||||||
env = os.environ.copy()
|
|
||||||
creationflags = 0
|
|
||||||
if sys.platform == "win32":
|
|
||||||
creationflags = (
|
|
||||||
subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS
|
|
||||||
)
|
|
||||||
subprocess.Popen(
|
|
||||||
[launcher],
|
|
||||||
start_new_session=True,
|
|
||||||
stdin=subprocess.DEVNULL,
|
|
||||||
stdout=subprocess.DEVNULL,
|
|
||||||
stderr=subprocess.DEVNULL,
|
|
||||||
env=env,
|
|
||||||
creationflags=creationflags,
|
|
||||||
)
|
|
||||||
|
|
||||||
mw.app.quit()
|
|
||||||
|
|
||||||
|
|
||||||
def _trigger_launcher_run() -> None:
|
|
||||||
"""Bump the mtime on pyproject.toml in the local data directory to trigger an update on next run."""
|
|
||||||
try:
|
|
||||||
# Get the local data directory equivalent to Rust's dirs::data_local_dir()
|
|
||||||
if is_win:
|
|
||||||
from .winpaths import get_local_appdata
|
|
||||||
|
|
||||||
data_dir = Path(get_local_appdata())
|
|
||||||
elif is_mac:
|
|
||||||
data_dir = Path.home() / "Library" / "Application Support"
|
|
||||||
else: # Linux
|
|
||||||
data_dir = Path(
|
|
||||||
os.environ.get("XDG_DATA_HOME", Path.home() / ".local" / "share")
|
|
||||||
)
|
|
||||||
|
|
||||||
pyproject_path = data_dir / "AnkiProgramFiles" / "pyproject.toml"
|
|
||||||
|
|
||||||
if pyproject_path.exists():
|
|
||||||
# Touch the file to update its mtime
|
|
||||||
pyproject_path.touch()
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
|
|
|
@ -8,29 +8,88 @@ import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import aqt.sound
|
|
||||||
from anki.utils import pointVersion
|
from anki.utils import pointVersion
|
||||||
from aqt import mw
|
from aqt import mw
|
||||||
from aqt.qt import QAction
|
from aqt.qt import QAction
|
||||||
from aqt.utils import askUser, is_mac, is_win, showInfo
|
from aqt.utils import askUser, is_mac, is_win, showInfo
|
||||||
|
|
||||||
|
|
||||||
def _anki_launcher_path() -> str | None:
|
def launcher_executable() -> str | None:
|
||||||
|
"""Return the path to the Anki launcher executable."""
|
||||||
return os.getenv("ANKI_LAUNCHER")
|
return os.getenv("ANKI_LAUNCHER")
|
||||||
|
|
||||||
|
|
||||||
def have_launcher() -> bool:
|
def uv_binary() -> str | None:
|
||||||
return _anki_launcher_path() is not None
|
"""Return the path to the uv binary."""
|
||||||
|
return os.environ.get("ANKI_LAUNCHER_UV")
|
||||||
|
|
||||||
|
|
||||||
|
def launcher_root() -> str | None:
|
||||||
|
"""Return the path to the launcher root directory (AnkiProgramFiles)."""
|
||||||
|
return os.environ.get("UV_PROJECT")
|
||||||
|
|
||||||
|
|
||||||
|
def venv_binary(cmd: str) -> str | None:
|
||||||
|
"""Return the path to a binary in the launcher's venv."""
|
||||||
|
root = launcher_root()
|
||||||
|
if not root:
|
||||||
|
return None
|
||||||
|
|
||||||
|
root_path = Path(root)
|
||||||
|
if is_win:
|
||||||
|
binary_path = root_path / ".venv" / "Scripts" / cmd
|
||||||
|
else:
|
||||||
|
binary_path = root_path / ".venv" / "bin" / cmd
|
||||||
|
|
||||||
|
return str(binary_path)
|
||||||
|
|
||||||
|
|
||||||
|
def add_python_requirements(reqs: list[str]) -> tuple[bool, str]:
|
||||||
|
"""Add Python requirements to the launcher venv using uv add.
|
||||||
|
|
||||||
|
Returns (success, output)"""
|
||||||
|
|
||||||
|
binary = uv_binary()
|
||||||
|
if not binary:
|
||||||
|
return (False, "Not in packaged build.")
|
||||||
|
|
||||||
|
uv_cmd = [binary, "add"] + reqs
|
||||||
|
result = subprocess.run(uv_cmd, capture_output=True, text=True, check=False)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
root = launcher_root()
|
||||||
|
if root:
|
||||||
|
sync_marker = Path(root) / ".sync_complete"
|
||||||
|
sync_marker.touch()
|
||||||
|
return (True, result.stdout)
|
||||||
|
else:
|
||||||
|
return (False, result.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
def trigger_launcher_run() -> None:
|
||||||
|
"""Bump the mtime on pyproject.toml in the local data directory to trigger an update on next run."""
|
||||||
|
try:
|
||||||
|
root = launcher_root()
|
||||||
|
if not root:
|
||||||
|
return
|
||||||
|
|
||||||
|
pyproject_path = Path(root) / "pyproject.toml"
|
||||||
|
|
||||||
|
if pyproject_path.exists():
|
||||||
|
# Touch the file to update its mtime
|
||||||
|
pyproject_path.touch()
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
|
||||||
def update_and_restart() -> None:
|
def update_and_restart() -> None:
|
||||||
from aqt import mw
|
"""Update and restart Anki using the launcher."""
|
||||||
|
launcher = launcher_executable()
|
||||||
launcher = _anki_launcher_path()
|
|
||||||
assert launcher
|
assert launcher
|
||||||
|
|
||||||
_trigger_launcher_run()
|
trigger_launcher_run()
|
||||||
|
|
||||||
with contextlib.suppress(ResourceWarning):
|
with contextlib.suppress(ResourceWarning):
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
|
@ -52,30 +111,6 @@ def update_and_restart() -> None:
|
||||||
mw.app.quit()
|
mw.app.quit()
|
||||||
|
|
||||||
|
|
||||||
def _trigger_launcher_run() -> None:
|
|
||||||
"""Bump the mtime on pyproject.toml in the local data directory to trigger an update on next run."""
|
|
||||||
try:
|
|
||||||
# Get the local data directory equivalent to Rust's dirs::data_local_dir()
|
|
||||||
if is_win:
|
|
||||||
from aqt.winpaths import get_local_appdata
|
|
||||||
|
|
||||||
data_dir = Path(get_local_appdata())
|
|
||||||
elif is_mac:
|
|
||||||
data_dir = Path.home() / "Library" / "Application Support"
|
|
||||||
else: # Linux
|
|
||||||
data_dir = Path(
|
|
||||||
os.environ.get("XDG_DATA_HOME", Path.home() / ".local" / "share")
|
|
||||||
)
|
|
||||||
|
|
||||||
pyproject_path = data_dir / "AnkiProgramFiles" / "pyproject.toml"
|
|
||||||
|
|
||||||
if pyproject_path.exists():
|
|
||||||
# Touch the file to update its mtime
|
|
||||||
pyproject_path.touch()
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
|
|
||||||
def confirm_then_upgrade():
|
def confirm_then_upgrade():
|
||||||
if not askUser("Change to a different Anki version?"):
|
if not askUser("Change to a different Anki version?"):
|
||||||
return
|
return
|
||||||
|
@ -119,7 +154,7 @@ def _packagedCmd(cmd: list[str]) -> tuple[Any, dict[str, str]]:
|
||||||
def setup():
|
def setup():
|
||||||
if pointVersion() >= 250600:
|
if pointVersion() >= 250600:
|
||||||
return
|
return
|
||||||
if not have_launcher():
|
if not launcher_executable():
|
||||||
return
|
return
|
||||||
|
|
||||||
# Add action to tools menu
|
# Add action to tools menu
|
||||||
|
@ -129,7 +164,21 @@ def setup():
|
||||||
|
|
||||||
# Monkey-patch audio tools to use anki-audio
|
# Monkey-patch audio tools to use anki-audio
|
||||||
if is_win or is_mac:
|
if is_win or is_mac:
|
||||||
|
import aqt
|
||||||
|
import aqt.sound
|
||||||
|
|
||||||
aqt.sound._packagedCmd = _packagedCmd
|
aqt.sound._packagedCmd = _packagedCmd
|
||||||
|
|
||||||
|
# Inject launcher functions into launcher module
|
||||||
|
import aqt.package
|
||||||
|
|
||||||
|
aqt.package.launcher_executable = launcher_executable
|
||||||
|
aqt.package.update_and_restart = update_and_restart
|
||||||
|
aqt.package.trigger_launcher_run = trigger_launcher_run
|
||||||
|
aqt.package.uv_binary = uv_binary
|
||||||
|
aqt.package.launcher_root = launcher_root
|
||||||
|
aqt.package.venv_binary = venv_binary
|
||||||
|
aqt.package.add_python_requirements = add_python_requirements
|
||||||
|
|
||||||
|
|
||||||
setup()
|
setup()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[project]
|
[project]
|
||||||
name = "anki-launcher"
|
name = "anki-launcher"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
description = "UV-based launcher for Anki."
|
description = "UV-based launcher for Anki."
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|
|
@ -128,7 +128,7 @@ fn run() -> Result<()> {
|
||||||
if !pyproject_has_changed {
|
if !pyproject_has_changed {
|
||||||
// If venv is already up to date, launch Anki normally
|
// If venv is already up to date, launch Anki normally
|
||||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||||
let cmd = build_python_command(&state.uv_install_root, &args)?;
|
let cmd = build_python_command(&state, &args)?;
|
||||||
launch_anki_normally(cmd)?;
|
launch_anki_normally(cmd)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,7 @@ fn run() -> Result<()> {
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
let cmd = build_python_command(&state.uv_install_root, &[])?;
|
let cmd = build_python_command(&state, &[])?;
|
||||||
platform::mac::prepare_for_launch_after_update(cmd, &uv_install_root)?;
|
platform::mac::prepare_for_launch_after_update(cmd, &uv_install_root)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -669,24 +669,35 @@ fn handle_uninstall(state: &State) -> Result<bool> {
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_python_command(uv_install_root: &std::path::Path, args: &[String]) -> Result<Command> {
|
fn build_python_command(state: &State, args: &[String]) -> Result<Command> {
|
||||||
let python_exe = if cfg!(target_os = "windows") {
|
let python_exe = if cfg!(target_os = "windows") {
|
||||||
let show_console = std::env::var("ANKI_CONSOLE").is_ok();
|
let show_console = std::env::var("ANKI_CONSOLE").is_ok();
|
||||||
if show_console {
|
if show_console {
|
||||||
uv_install_root.join(".venv/Scripts/python.exe")
|
state.uv_install_root.join(".venv/Scripts/python.exe")
|
||||||
} else {
|
} else {
|
||||||
uv_install_root.join(".venv/Scripts/pythonw.exe")
|
state.uv_install_root.join(".venv/Scripts/pythonw.exe")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uv_install_root.join(".venv/bin/python")
|
state.uv_install_root.join(".venv/bin/python")
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut cmd = Command::new(python_exe);
|
let mut cmd = Command::new(&python_exe);
|
||||||
cmd.args(["-c", "import aqt, sys; sys.argv[0] = 'Anki'; aqt.run()"]);
|
cmd.args(["-c", "import aqt, sys; sys.argv[0] = 'Anki'; aqt.run()"]);
|
||||||
cmd.args(args);
|
cmd.args(args);
|
||||||
// tell the Python code it was invoked by the launcher, and updating is
|
// tell the Python code it was invoked by the launcher, and updating is
|
||||||
// available
|
// available
|
||||||
cmd.env("ANKI_LAUNCHER", std::env::current_exe()?.utf8()?.as_str());
|
cmd.env("ANKI_LAUNCHER", std::env::current_exe()?.utf8()?.as_str());
|
||||||
|
|
||||||
|
// Set UV and Python paths for the Python code
|
||||||
|
let (exe_dir, _) = get_exe_and_resources_dirs()?;
|
||||||
|
let uv_path = exe_dir.join(get_uv_binary_name());
|
||||||
|
cmd.env("ANKI_LAUNCHER_UV", uv_path.utf8()?.as_str());
|
||||||
|
cmd.env("UV_PROJECT", state.uv_install_root.utf8()?.as_str());
|
||||||
|
|
||||||
|
// Set UV_PRERELEASE=allow if beta mode is enabled
|
||||||
|
if state.prerelease_marker.exists() {
|
||||||
|
cmd.env("UV_PRERELEASE", "allow");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(cmd)
|
Ok(cmd)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue