mirror of
https://github.com/ankitects/anki.git
synced 2025-11-06 04:37:22 -05:00
Merge fd1cbd38b0 into dac26ce671
This commit is contained in:
commit
fe878d8bba
10 changed files with 626 additions and 14 deletions
|
|
@ -142,7 +142,7 @@ walkdir = "2.5.0"
|
|||
which = "8.0.0"
|
||||
widestring = "1.1.0"
|
||||
winapi = { version = "0.3", features = ["wincon", "winreg"] }
|
||||
windows = { version = "0.61.3", features = ["Media_SpeechSynthesis", "Media_Core", "Foundation_Collections", "Storage_Streams", "Win32_System_Console", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_Foundation", "Win32_UI_Shell", "Wdk_System_SystemServices"] }
|
||||
windows = { version = "0.61.3", features = ["Media_SpeechSynthesis", "Media_Core", "Foundation_Collections", "Storage_Streams", "Win32_System_Console", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_Foundation", "Win32_UI_Shell", "Wdk_System_SystemServices", "Win32_System_LibraryLoader"] }
|
||||
wiremock = "0.6.3"
|
||||
xz2 = "0.1.7"
|
||||
zip = { version = "4.1.0", default-features = false, features = ["deflate", "time"] }
|
||||
|
|
|
|||
|
|
@ -14,16 +14,13 @@ anki_process.workspace = true
|
|||
anyhow.workspace = true
|
||||
camino.workspace = true
|
||||
dirs.workspace = true
|
||||
libc.workspace = true
|
||||
locale_config.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies]
|
||||
libc.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows.workspace = true
|
||||
widestring.workspace = true
|
||||
libc.workspace = true
|
||||
libc-stdhandle.workspace = true
|
||||
|
||||
[[bin]]
|
||||
|
|
|
|||
12
qt/launcher/src/libpython_nix.py
Normal file
12
qt/launcher/src/libpython_nix.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Copyright: Ankitects Pty Ltd and contributors
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
import os
|
||||
import sysconfig
|
||||
|
||||
cfg = sysconfig.get_config_var
|
||||
base = cfg("installed_base") or cfg("installed_platbase")
|
||||
lib = cfg("LDLIBRARY") or cfg("INSTSONAME")
|
||||
version = cfg("py_version_nodot")
|
||||
print(version)
|
||||
print(os.path.join(base, "lib", lib))
|
||||
12
qt/launcher/src/libpython_win.py
Normal file
12
qt/launcher/src/libpython_win.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Copyright: Ankitects Pty Ltd and contributors
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
import os
|
||||
import sysconfig
|
||||
|
||||
cfg = sysconfig.get_config_var
|
||||
base = cfg("installed_base") or cfg("installed_platbase")
|
||||
version = cfg("py_version_nodot")
|
||||
lib = "python" + version + ".dll"
|
||||
print(version)
|
||||
print(os.path.join(base, lib))
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#![windows_subsystem = "windows"]
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::io::stdin;
|
||||
use std::io::stdout;
|
||||
use std::io::Write;
|
||||
|
|
@ -19,6 +20,7 @@ use anki_io::remove_file;
|
|||
use anki_io::write_file;
|
||||
use anki_io::ToUtf8Path;
|
||||
use anki_process::CommandExt as AnkiCommandExt;
|
||||
use anyhow::anyhow;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
|
||||
|
|
@ -28,6 +30,7 @@ use crate::platform::get_exe_and_resources_dirs;
|
|||
use crate::platform::get_uv_binary_name;
|
||||
use crate::platform::launch_anki_normally;
|
||||
use crate::platform::respawn_launcher;
|
||||
use crate::platform::run_anki_normally;
|
||||
|
||||
mod platform;
|
||||
|
||||
|
|
@ -53,6 +56,7 @@ struct State {
|
|||
previous_version: Option<String>,
|
||||
resources_dir: std::path::PathBuf,
|
||||
venv_folder: std::path::PathBuf,
|
||||
libpython_info: std::path::PathBuf,
|
||||
/// system Python + PyQt6 library mode
|
||||
system_qt: bool,
|
||||
}
|
||||
|
|
@ -132,6 +136,7 @@ fn run() -> Result<()> {
|
|||
&& resources_dir.join("system_qt").exists(),
|
||||
resources_dir,
|
||||
venv_folder: uv_install_root.join(".venv"),
|
||||
libpython_info: uv_install_root.join(".cached-info"),
|
||||
};
|
||||
|
||||
// Check for uninstall request from Windows uninstaller
|
||||
|
|
@ -156,9 +161,11 @@ fn run() -> Result<()> {
|
|||
|
||||
if !launcher_requested && !pyproject_has_changed && !different_launcher {
|
||||
// If no launcher request and venv is already up to date, launch Anki normally
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
let cmd = build_python_command(&state, &args)?;
|
||||
launch_anki_normally(cmd)?;
|
||||
if std::env::var("ANKI_LAUNCHER_NO_EMBED").is_ok() || !run_anki_normally(&state) {
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
let cmd = build_python_command(&state, &args)?;
|
||||
launch_anki_normally(cmd)?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
|
@ -274,6 +281,9 @@ fn handle_version_install_or_update(state: &State, choice: MainMenuChoice) -> Re
|
|||
// Remove sync marker before attempting sync
|
||||
let _ = remove_file(&state.sync_complete_marker);
|
||||
|
||||
// clear possibly invalidated ibpython info cache
|
||||
let _ = remove_file(&state.libpython_info);
|
||||
|
||||
println!("{}\n", state.tr.launcher_updating_anki());
|
||||
|
||||
let python_version_trimmed = if state.user_python_version_path.exists() {
|
||||
|
|
@ -1031,8 +1041,8 @@ fn uv_command(state: &State) -> Result<Command> {
|
|||
Ok(command)
|
||||
}
|
||||
|
||||
fn build_python_command(state: &State, args: &[String]) -> Result<Command> {
|
||||
let python_exe = if cfg!(target_os = "windows") {
|
||||
fn get_venv_bin_path(state: &State) -> std::path::PathBuf {
|
||||
if cfg!(target_os = "windows") {
|
||||
let show_console = std::env::var("ANKI_CONSOLE").is_ok();
|
||||
if show_console {
|
||||
state.venv_folder.join("Scripts/python.exe")
|
||||
|
|
@ -1041,11 +1051,11 @@ fn build_python_command(state: &State, args: &[String]) -> Result<Command> {
|
|||
}
|
||||
} else {
|
||||
state.venv_folder.join("bin/python")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let mut cmd = Command::new(&python_exe);
|
||||
cmd.args(["-c", "import aqt, sys; sys.argv[0] = 'Anki'; aqt.run()"]);
|
||||
cmd.args(args);
|
||||
fn _build_python_command(state: &State, python_exe: &std::path::Path) -> Result<Command> {
|
||||
let mut cmd = Command::new(python_exe);
|
||||
// tell the Python code it was invoked by the launcher, and updating is
|
||||
// available
|
||||
cmd.env("ANKI_LAUNCHER", std::env::current_exe()?.utf8()?.as_str());
|
||||
|
|
@ -1058,6 +1068,82 @@ fn build_python_command(state: &State, args: &[String]) -> Result<Command> {
|
|||
Ok(cmd)
|
||||
}
|
||||
|
||||
fn build_python_command(state: &State, args: &[String]) -> Result<Command> {
|
||||
let python_exe = get_venv_bin_path(state);
|
||||
let mut cmd = _build_python_command(state, &python_exe)?;
|
||||
cmd.args(["-c", "import aqt, sys; sys.argv[0] = 'Anki'; aqt.run()"]);
|
||||
cmd.args(args);
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
fn get_python_env_info(state: &State) -> Result<(String, std::path::PathBuf, CString)> {
|
||||
let python_exe = get_venv_bin_path(state);
|
||||
// we can cache this, as it can only change after syncing the project
|
||||
// as it stands, we already expect there to be a trusted python exe in
|
||||
// a particular place so this doesn't seem too concerning security-wise
|
||||
// TODO: let-chains...
|
||||
if let Ok(cached) = read_file(&state.libpython_info) {
|
||||
if let Ok(cached) = String::from_utf8(cached) {
|
||||
if let Some((version, lib_path)) = cached.split_once('\n') {
|
||||
if let Ok(lib_path) = state.uv_install_root.join(lib_path.trim()).canonicalize() {
|
||||
// make sure we're still within AnkiProgramFiles...
|
||||
if lib_path.strip_prefix(&state.uv_install_root).is_ok() {
|
||||
return Ok((
|
||||
version.trim().to_string(),
|
||||
lib_path,
|
||||
CString::new(python_exe.as_os_str().as_encoded_bytes())?,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let _ = remove_file(&state.libpython_info);
|
||||
}
|
||||
|
||||
let mut cmd = _build_python_command(state, &python_exe)?;
|
||||
// NOTE:
|
||||
// we can check which sysconfig vars are available
|
||||
// with `sysconfig.get_config_vars()`. very limited on
|
||||
// windows pre-3.13 (probably because no ./configure)
|
||||
// from what i've found, `installed_base` seems to be
|
||||
// available on 3.9/3.13 on both windows and linux.
|
||||
// `LIBDIR` and `LDLIBRARY` aren't present on 3.9/win.
|
||||
// on win, we can't use python3.dll, only python3XX.dll
|
||||
let script = if cfg!(windows) {
|
||||
include_str!("libpython_win.py")
|
||||
} else {
|
||||
include_str!("libpython_nix.py")
|
||||
}
|
||||
.trim();
|
||||
|
||||
cmd.args(["-c", script]);
|
||||
|
||||
let output = cmd.utf8_output()?;
|
||||
let output = output.stdout.trim();
|
||||
|
||||
let (version, lib_path) = output
|
||||
.split_once('\n')
|
||||
.ok_or_else(|| anyhow!("invalid libpython info"))?;
|
||||
let lib_path = std::path::PathBuf::from(lib_path.trim());
|
||||
|
||||
if !lib_path.exists() {
|
||||
anyhow::bail!("library path doesn't exist: {lib_path:?}");
|
||||
}
|
||||
|
||||
if let Ok(lib_path) = lib_path.strip_prefix(&state.uv_install_root) {
|
||||
let _ = write_file(
|
||||
&state.libpython_info,
|
||||
format!("{version}\n{}", lib_path.display()),
|
||||
);
|
||||
}
|
||||
|
||||
Ok((
|
||||
version.trim().to_owned(),
|
||||
lib_path,
|
||||
CString::new(python_exe.as_os_str().as_encoded_bytes())?,
|
||||
))
|
||||
}
|
||||
|
||||
fn is_mirror_enabled(state: &State) -> bool {
|
||||
state.mirror_path.exists()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,19 @@ pub mod mac;
|
|||
#[cfg(target_os = "windows")]
|
||||
pub mod windows;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub mod nix;
|
||||
|
||||
mod py313;
|
||||
mod py39;
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::ffi::CString;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anki_process::CommandExt;
|
||||
use anyhow::anyhow;
|
||||
use anyhow::ensure;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
|
||||
|
|
@ -109,6 +119,30 @@ pub fn launch_anki_normally(mut cmd: std::process::Command) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn _run_anki_normally(state: &crate::State) -> Result<()> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let console = std::env::var("ANKI_CONSOLE").is_ok();
|
||||
if console {
|
||||
// no pythonw.exe available for us to use
|
||||
ensure_terminal_shown()?;
|
||||
}
|
||||
crate::platform::windows::prepare_to_launch_normally();
|
||||
windows::run(state, console)?;
|
||||
}
|
||||
#[cfg(unix)]
|
||||
nix::run(state)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_anki_normally(state: &crate::State) -> bool {
|
||||
if let Err(e) = _run_anki_normally(state) {
|
||||
eprintln!("failed to run as embedded: {e:?}");
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub use windows::ensure_terminal_shown;
|
||||
|
||||
|
|
@ -139,3 +173,131 @@ pub fn ensure_os_supported() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub type PyIsInitialized = extern "C" fn() -> std::ffi::c_int;
|
||||
pub type PyRunSimpleString = extern "C" fn(command: *const std::ffi::c_char) -> std::ffi::c_int;
|
||||
pub type PyFinalizeEx = extern "C" fn() -> std::ffi::c_int;
|
||||
pub type PyConfigInitPythonConfig = extern "C" fn(*mut std::ffi::c_void);
|
||||
// WARN: py39 and py313's PyStatus are identical
|
||||
// check if this remains true in future versions
|
||||
pub type PyConfigSetBytesString = extern "C" fn(
|
||||
config: *mut std::ffi::c_void,
|
||||
config_str: *mut *mut libc::wchar_t,
|
||||
str_: *const std::os::raw::c_char,
|
||||
) -> py313::PyStatus;
|
||||
pub type PyConfigSetBytesArgv = extern "C" fn(
|
||||
config: *mut std::ffi::c_void,
|
||||
argc: isize,
|
||||
argv: *const *mut std::os::raw::c_char,
|
||||
) -> py313::PyStatus;
|
||||
pub type PyInitializeFromConfig = extern "C" fn(*const std::ffi::c_void) -> py313::PyStatus;
|
||||
pub type PyStatusException = extern "C" fn(err: py313::PyStatus) -> std::os::raw::c_int;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
struct PyFfi {
|
||||
exec: CString,
|
||||
lib: *mut std::ffi::c_void,
|
||||
Py_IsInitialized: PyIsInitialized,
|
||||
PyRun_SimpleString: PyRunSimpleString,
|
||||
Py_FinalizeEx: PyFinalizeEx,
|
||||
PyConfig_InitPythonConfig: PyConfigInitPythonConfig,
|
||||
PyConfig_SetBytesString: PyConfigSetBytesString,
|
||||
Py_InitializeFromConfig: PyInitializeFromConfig,
|
||||
PyConfig_SetBytesArgv: PyConfigSetBytesArgv,
|
||||
PyStatus_Exception: PyStatusException,
|
||||
}
|
||||
|
||||
trait PyConfigExt {
|
||||
fn init(ffi: &PyFfi) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
fn set_exec(&mut self, ffi: &PyFfi) -> Result<&mut Self>;
|
||||
fn set_argv(&mut self, ffi: &PyFfi) -> Result<&mut Self>;
|
||||
}
|
||||
|
||||
macro_rules! impl_pyconfig {
|
||||
($($pyconfig:path),* $(,)*) => {
|
||||
$(impl PyConfigExt for $pyconfig {
|
||||
fn init(ffi: &PyFfi) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut config: Self = unsafe { std::mem::zeroed() };
|
||||
(ffi.PyConfig_InitPythonConfig)(&mut config as *const _ as *mut _);
|
||||
config.parse_argv = 0;
|
||||
config.install_signal_handlers = 1;
|
||||
config
|
||||
}
|
||||
|
||||
fn set_exec(&mut self, ffi: &PyFfi) -> Result<&mut Self> {
|
||||
let status = (ffi.PyConfig_SetBytesString)(
|
||||
self as *const _ as *mut _,
|
||||
&mut self.executable,
|
||||
ffi.exec.as_ptr(),
|
||||
);
|
||||
ensure!(
|
||||
(ffi.PyStatus_Exception)(status) == 0,
|
||||
"failed to set config"
|
||||
);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn set_argv(&mut self, ffi: &PyFfi) -> Result<&mut Self> {
|
||||
let argv = std::env::args_os()
|
||||
.map(|x| CString::new(x.as_encoded_bytes()))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|_| anyhow!("unable to construct argv"))?;
|
||||
let argvp = argv
|
||||
.iter()
|
||||
.map(|x| x.as_ptr() as *mut i8)
|
||||
.collect::<Vec<_>>();
|
||||
let status = (ffi.PyConfig_SetBytesArgv)(
|
||||
self as *mut _ as *mut _,
|
||||
argvp.len() as isize,
|
||||
argvp.as_ptr() as *mut _,
|
||||
);
|
||||
ensure!((ffi.PyStatus_Exception)(status) == 0, "failed to set argv");
|
||||
Ok(self)
|
||||
}
|
||||
})*
|
||||
};
|
||||
}
|
||||
|
||||
impl_pyconfig![py39::PyConfig, py313::PyConfig];
|
||||
|
||||
impl PyFfi {
|
||||
fn run(self, version: &str, preamble: Option<&CStr>) -> Result<()> {
|
||||
match version {
|
||||
"39" => {
|
||||
let mut config = py39::PyConfig::init(&self);
|
||||
config.set_exec(&self)?.set_argv(&self)?;
|
||||
(self.Py_InitializeFromConfig)(&config as *const _ as *const _);
|
||||
}
|
||||
"313" => {
|
||||
let mut config = py313::PyConfig::init(&self);
|
||||
config.set_exec(&self)?.set_argv(&self)?;
|
||||
(self.Py_InitializeFromConfig)(&config as *const _ as *const _);
|
||||
}
|
||||
_ => Err(anyhow!("unsupported python version: {version}"))?,
|
||||
};
|
||||
|
||||
ensure!(
|
||||
(self.Py_IsInitialized)() == 1,
|
||||
"interpreter was not initialised!"
|
||||
);
|
||||
|
||||
if let Some(preamble) = preamble {
|
||||
let res = (self.PyRun_SimpleString)(preamble.as_ptr());
|
||||
ensure!(res == 0, "failed to run preamble");
|
||||
}
|
||||
|
||||
// test importing aqt first before falling back to usual launch
|
||||
let res = (self.PyRun_SimpleString)(c"import aqt".as_ptr());
|
||||
ensure!(res == 0, "failed to import aqt");
|
||||
|
||||
// from here on, don't fallback if we fail
|
||||
let _ = (self.PyRun_SimpleString)(c"aqt.run()".as_ptr());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
82
qt/launcher/src/platform/nix.rs
Normal file
82
qt/launcher/src/platform/nix.rs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::ffi::CString;
|
||||
use std::os::unix::prelude::OsStrExt;
|
||||
|
||||
use anki_io::ToUtf8Path;
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::get_python_env_info;
|
||||
use crate::platform::PyFfi;
|
||||
use crate::State;
|
||||
|
||||
impl Drop for PyFfi {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(self.Py_FinalizeEx)();
|
||||
libc::dlclose(self.lib)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! load_sym {
|
||||
($lib:expr, $name:expr) => {{
|
||||
libc::dlerror();
|
||||
let sym = libc::dlsym($lib, $name.as_ptr());
|
||||
if sym.is_null() {
|
||||
let dlerror_str = CStr::from_ptr(libc::dlerror()).to_str()?;
|
||||
anyhow::bail!("failed to load {}: {dlerror_str}", $name.to_string_lossy());
|
||||
}
|
||||
std::mem::transmute(sym)
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! ffi {
|
||||
($lib:expr, $exec:expr, $($field:ident),* $(,)?) => {
|
||||
#[allow(clippy::missing_transmute_annotations)] // they're not missing
|
||||
PyFfi { exec: $exec, $($field: load_sym!($lib, ::std::ffi::CString::new(stringify!($field)).map_err(|_| anyhow::anyhow!("failed to construct symbol CString"))?),)* lib: $lib, }
|
||||
};
|
||||
}
|
||||
|
||||
impl PyFfi {
|
||||
#[allow(non_snake_case)]
|
||||
pub fn load(path: impl AsRef<std::path::Path>, exec: CString) -> Result<Self> {
|
||||
unsafe {
|
||||
libc::dlerror();
|
||||
let lib = libc::dlopen(
|
||||
CString::new(path.as_ref().as_os_str().as_bytes())?.as_ptr(),
|
||||
libc::RTLD_LAZY | libc::RTLD_GLOBAL,
|
||||
);
|
||||
if lib.is_null() {
|
||||
let dlerror_str = CStr::from_ptr(libc::dlerror()).to_str()?;
|
||||
anyhow::bail!("failed to load library: {dlerror_str}");
|
||||
}
|
||||
|
||||
Ok(ffi!(
|
||||
lib,
|
||||
exec,
|
||||
Py_IsInitialized,
|
||||
PyRun_SimpleString,
|
||||
Py_FinalizeEx,
|
||||
PyConfig_InitPythonConfig,
|
||||
PyConfig_SetBytesString,
|
||||
Py_InitializeFromConfig,
|
||||
PyConfig_SetBytesArgv,
|
||||
PyStatus_Exception
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(state: &State) -> Result<()> {
|
||||
let (version, lib_path, exec) = get_python_env_info(state)?;
|
||||
|
||||
std::env::set_var("ANKI_LAUNCHER", std::env::current_exe()?.utf8()?.as_str());
|
||||
std::env::set_var("ANKI_LAUNCHER_UV", state.uv_path.utf8()?.as_str());
|
||||
std::env::set_var("UV_PROJECT", state.uv_install_root.utf8()?.as_str());
|
||||
std::env::remove_var("SSLKEYLOGFILE");
|
||||
|
||||
PyFfi::load(lib_path, exec)?.run(&version, None)
|
||||
}
|
||||
93
qt/launcher/src/platform/py313.rs
Normal file
93
qt/launcher/src/platform/py313.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
use libc::wchar_t;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct PyStatus {
|
||||
pub _type: ::std::os::raw::c_uint,
|
||||
pub func: *const ::std::os::raw::c_char,
|
||||
pub err_msg: *const ::std::os::raw::c_char,
|
||||
pub exitcode: ::std::os::raw::c_int,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct PyWideStringList {
|
||||
pub length: isize,
|
||||
pub items: *mut *mut wchar_t,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct PyConfig {
|
||||
pub _config_init: ::std::os::raw::c_int,
|
||||
pub isolated: ::std::os::raw::c_int,
|
||||
pub use_environment: ::std::os::raw::c_int,
|
||||
pub dev_mode: ::std::os::raw::c_int,
|
||||
pub install_signal_handlers: ::std::os::raw::c_int,
|
||||
pub use_hash_seed: ::std::os::raw::c_int,
|
||||
pub hash_seed: ::std::os::raw::c_ulong,
|
||||
pub faulthandler: ::std::os::raw::c_int,
|
||||
pub tracemalloc: ::std::os::raw::c_int,
|
||||
pub perf_profiling: ::std::os::raw::c_int,
|
||||
pub import_time: ::std::os::raw::c_int,
|
||||
pub code_debug_ranges: ::std::os::raw::c_int,
|
||||
pub show_ref_count: ::std::os::raw::c_int,
|
||||
pub dump_refs: ::std::os::raw::c_int,
|
||||
pub dump_refs_file: *mut wchar_t,
|
||||
pub malloc_stats: ::std::os::raw::c_int,
|
||||
pub filesystem_encoding: *mut wchar_t,
|
||||
pub filesystem_errors: *mut wchar_t,
|
||||
pub pycache_prefix: *mut wchar_t,
|
||||
pub parse_argv: ::std::os::raw::c_int,
|
||||
pub orig_argv: PyWideStringList,
|
||||
pub argv: PyWideStringList,
|
||||
pub xoptions: PyWideStringList,
|
||||
pub warnoptions: PyWideStringList,
|
||||
pub site_import: ::std::os::raw::c_int,
|
||||
pub bytes_warning: ::std::os::raw::c_int,
|
||||
pub warn_default_encoding: ::std::os::raw::c_int,
|
||||
pub inspect: ::std::os::raw::c_int,
|
||||
pub interactive: ::std::os::raw::c_int,
|
||||
pub optimization_level: ::std::os::raw::c_int,
|
||||
pub parser_debug: ::std::os::raw::c_int,
|
||||
pub write_bytecode: ::std::os::raw::c_int,
|
||||
pub verbose: ::std::os::raw::c_int,
|
||||
pub quiet: ::std::os::raw::c_int,
|
||||
pub user_site_directory: ::std::os::raw::c_int,
|
||||
pub configure_c_stdio: ::std::os::raw::c_int,
|
||||
pub buffered_stdio: ::std::os::raw::c_int,
|
||||
pub stdio_encoding: *mut wchar_t,
|
||||
pub stdio_errors: *mut wchar_t,
|
||||
#[cfg(windows)]
|
||||
pub legacy_windows_stdio: ::std::os::raw::c_int,
|
||||
pub check_hash_pycs_mode: *mut wchar_t,
|
||||
pub use_frozen_modules: ::std::os::raw::c_int,
|
||||
pub safe_path: ::std::os::raw::c_int,
|
||||
pub int_max_str_digits: ::std::os::raw::c_int,
|
||||
pub cpu_count: ::std::os::raw::c_int,
|
||||
pub pathconfig_warnings: ::std::os::raw::c_int,
|
||||
pub program_name: *mut wchar_t,
|
||||
pub pythonpath_env: *mut wchar_t,
|
||||
pub home: *mut wchar_t,
|
||||
pub platlibdir: *mut wchar_t,
|
||||
pub module_search_paths_set: ::std::os::raw::c_int,
|
||||
pub module_search_paths: PyWideStringList,
|
||||
pub stdlib_dir: *mut wchar_t,
|
||||
pub executable: *mut wchar_t,
|
||||
pub base_executable: *mut wchar_t,
|
||||
pub prefix: *mut wchar_t,
|
||||
pub base_prefix: *mut wchar_t,
|
||||
pub exec_prefix: *mut wchar_t,
|
||||
pub base_exec_prefix: *mut wchar_t,
|
||||
pub skip_source_first_line: ::std::os::raw::c_int,
|
||||
pub run_command: *mut wchar_t,
|
||||
pub run_module: *mut wchar_t,
|
||||
pub run_filename: *mut wchar_t,
|
||||
pub sys_path_0: *mut wchar_t,
|
||||
pub _install_importlib: ::std::os::raw::c_int,
|
||||
pub _init_main: ::std::os::raw::c_int,
|
||||
pub _is_python_build: ::std::os::raw::c_int,
|
||||
}
|
||||
75
qt/launcher/src/platform/py39.rs
Normal file
75
qt/launcher/src/platform/py39.rs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
use libc::wchar_t;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct PyWideStringList {
|
||||
pub length: ::std::os::raw::c_longlong,
|
||||
pub items: *mut *mut wchar_t,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct PyConfig {
|
||||
pub _config_init: ::std::os::raw::c_int,
|
||||
pub isolated: ::std::os::raw::c_int,
|
||||
pub use_environment: ::std::os::raw::c_int,
|
||||
pub dev_mode: ::std::os::raw::c_int,
|
||||
pub install_signal_handlers: ::std::os::raw::c_int,
|
||||
pub use_hash_seed: ::std::os::raw::c_int,
|
||||
pub hash_seed: ::std::os::raw::c_ulong,
|
||||
pub faulthandler: ::std::os::raw::c_int,
|
||||
pub _use_peg_parser: ::std::os::raw::c_int,
|
||||
pub tracemalloc: ::std::os::raw::c_int,
|
||||
pub import_time: ::std::os::raw::c_int,
|
||||
pub show_ref_count: ::std::os::raw::c_int,
|
||||
pub dump_refs: ::std::os::raw::c_int,
|
||||
pub malloc_stats: ::std::os::raw::c_int,
|
||||
pub filesystem_encoding: *mut wchar_t,
|
||||
pub filesystem_errors: *mut wchar_t,
|
||||
pub pycache_prefix: *mut wchar_t,
|
||||
pub parse_argv: ::std::os::raw::c_int,
|
||||
pub argv: PyWideStringList,
|
||||
pub program_name: *mut wchar_t,
|
||||
pub xoptions: PyWideStringList,
|
||||
pub warnoptions: PyWideStringList,
|
||||
pub site_import: ::std::os::raw::c_int,
|
||||
pub bytes_warning: ::std::os::raw::c_int,
|
||||
pub inspect: ::std::os::raw::c_int,
|
||||
pub interactive: ::std::os::raw::c_int,
|
||||
pub optimization_level: ::std::os::raw::c_int,
|
||||
pub parser_debug: ::std::os::raw::c_int,
|
||||
pub write_bytecode: ::std::os::raw::c_int,
|
||||
pub verbose: ::std::os::raw::c_int,
|
||||
pub quiet: ::std::os::raw::c_int,
|
||||
pub user_site_directory: ::std::os::raw::c_int,
|
||||
pub configure_c_stdio: ::std::os::raw::c_int,
|
||||
pub buffered_stdio: ::std::os::raw::c_int,
|
||||
pub stdio_encoding: *mut wchar_t,
|
||||
pub stdio_errors: *mut wchar_t,
|
||||
#[cfg(windows)]
|
||||
pub legacy_windows_stdio: ::std::os::raw::c_int,
|
||||
pub check_hash_pycs_mode: *mut wchar_t,
|
||||
pub pathconfig_warnings: ::std::os::raw::c_int,
|
||||
pub pythonpath_env: *mut wchar_t,
|
||||
pub home: *mut wchar_t,
|
||||
pub module_search_paths_set: ::std::os::raw::c_int,
|
||||
pub module_search_paths: PyWideStringList,
|
||||
pub executable: *mut wchar_t,
|
||||
pub base_executable: *mut wchar_t,
|
||||
pub prefix: *mut wchar_t,
|
||||
pub base_prefix: *mut wchar_t,
|
||||
pub exec_prefix: *mut wchar_t,
|
||||
pub base_exec_prefix: *mut wchar_t,
|
||||
pub platlibdir: *mut wchar_t,
|
||||
pub skip_source_first_line: ::std::os::raw::c_int,
|
||||
pub run_command: *mut wchar_t,
|
||||
pub run_module: *mut wchar_t,
|
||||
pub run_filename: *mut wchar_t,
|
||||
pub _install_importlib: ::std::os::raw::c_int,
|
||||
pub _init_main: ::std::os::raw::c_int,
|
||||
pub _isolated_interpreter: ::std::os::raw::c_int,
|
||||
pub _orig_argv: PyWideStringList,
|
||||
}
|
||||
|
|
@ -1,17 +1,27 @@
|
|||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::io::stdin;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::process::Command;
|
||||
|
||||
use anki_io::ToUtf8Path;
|
||||
use anyhow::anyhow;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use widestring::u16cstr;
|
||||
use windows::core::PCSTR;
|
||||
use windows::core::PCWSTR;
|
||||
use windows::Wdk::System::SystemServices::RtlGetVersion;
|
||||
use windows::Win32::Foundation::FreeLibrary;
|
||||
use windows::Win32::Foundation::HMODULE;
|
||||
use windows::Win32::System::Console::AttachConsole;
|
||||
use windows::Win32::System::Console::GetConsoleWindow;
|
||||
use windows::Win32::System::Console::ATTACH_PARENT_PROCESS;
|
||||
use windows::Win32::System::LibraryLoader::GetProcAddress;
|
||||
use windows::Win32::System::LibraryLoader::LoadLibraryExW;
|
||||
use windows::Win32::System::LibraryLoader::LOAD_LIBRARY_FLAGS;
|
||||
use windows::Win32::System::Registry::RegCloseKey;
|
||||
use windows::Win32::System::Registry::RegOpenKeyExW;
|
||||
use windows::Win32::System::Registry::RegQueryValueExW;
|
||||
|
|
@ -22,6 +32,10 @@ use windows::Win32::System::Registry::REG_SZ;
|
|||
use windows::Win32::System::SystemInformation::OSVERSIONINFOW;
|
||||
use windows::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID;
|
||||
|
||||
use crate::get_python_env_info;
|
||||
use crate::platform::PyFfi;
|
||||
use crate::State;
|
||||
|
||||
/// Returns true if running on Windows 10 (not Windows 11)
|
||||
fn is_windows_10() -> bool {
|
||||
unsafe {
|
||||
|
|
@ -261,3 +275,82 @@ pub fn prepare_to_launch_normally() {
|
|||
|
||||
attach_to_parent_console();
|
||||
}
|
||||
|
||||
impl Drop for PyFfi {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(self.Py_FinalizeEx)();
|
||||
let _ = FreeLibrary(HMODULE(self.lib));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! load_sym {
|
||||
($lib:expr, $name:expr) => {
|
||||
std::mem::transmute(
|
||||
GetProcAddress($lib, PCSTR::from_raw($name.as_ptr().cast()))
|
||||
.ok_or_else(|| anyhow!("failed to load {}", $name.to_string_lossy()))?,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! ffi {
|
||||
($lib:expr, $exec:expr, $($field:ident),* $(,)?) => {
|
||||
#[allow(clippy::missing_transmute_annotations)] // they're not missing
|
||||
PyFfi { exec: $exec, $($field: {
|
||||
let sym = ::std::ffi::CString::new(stringify!($field)).map_err(|_| anyhow::anyhow!("failed to construct symbol CString"))?;
|
||||
load_sym!($lib, sym)
|
||||
},)* lib: $lib.0, }
|
||||
};
|
||||
}
|
||||
|
||||
impl PyFfi {
|
||||
#[allow(non_snake_case)]
|
||||
pub fn load(path: impl AsRef<std::path::Path>, exec: CString) -> Result<Self> {
|
||||
unsafe {
|
||||
let wide_filename: Vec<u16> = path
|
||||
.as_ref()
|
||||
.as_os_str()
|
||||
.encode_wide()
|
||||
.chain(Some(0))
|
||||
.collect();
|
||||
|
||||
let lib = LoadLibraryExW(
|
||||
PCWSTR::from_raw(wide_filename.as_ptr()),
|
||||
None,
|
||||
LOAD_LIBRARY_FLAGS::default(),
|
||||
)?;
|
||||
|
||||
Ok(ffi! {
|
||||
lib,
|
||||
exec,
|
||||
Py_IsInitialized,
|
||||
PyRun_SimpleString,
|
||||
Py_FinalizeEx,
|
||||
PyConfig_InitPythonConfig,
|
||||
PyConfig_SetBytesString,
|
||||
Py_InitializeFromConfig,
|
||||
PyConfig_SetBytesArgv,
|
||||
PyStatus_Exception,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(state: &State, console: bool) -> Result<()> {
|
||||
let (version, lib_path, exec) = get_python_env_info(state)?;
|
||||
|
||||
std::env::set_var("ANKI_LAUNCHER", std::env::current_exe()?.utf8()?.as_str());
|
||||
std::env::set_var("ANKI_LAUNCHER_UV", state.uv_path.utf8()?.as_str());
|
||||
std::env::set_var("UV_PROJECT", state.uv_install_root.utf8()?.as_str());
|
||||
std::env::remove_var("SSLKEYLOGFILE");
|
||||
|
||||
// NOTE: without windows_subsystem=console or pythonw,
|
||||
// we need to reconnect stdin/stdout/stderr within the interp
|
||||
// reconnect_stdio_to_console doesn't make a difference here
|
||||
let preamble = console.then_some(
|
||||
cr#"import sys; sys.stdout = sys.stderr = open("CONOUT$", "w"); sys.stdin = open("CONIN$", "r");"#,
|
||||
);
|
||||
|
||||
PyFfi::load(lib_path, exec)?.run(&version, preamble)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue