use eyre::Result;
use itertools::sorted;
use std::env::consts::{ARCH, OS};
use crate::{backend, config, dirs, env, file};
use crate::{config::Config, env::PYENV_ROOT};
use super::reconcile;
#[derive(Debug, usage_rs::Args)]
#[usage(
verbatim_doc_comment,
example(
r###"pyenv install 3.11.0
mise sync python --pyenv
mise use -g python@3.11.0 # uses pyenv-provided python"###
),
example(
r###"uv python install 3.11.0
mise install python@3.10.0
mise sync python --uv
mise x python@3.11.0 -- python -V # uses uv-provided python
uv run -p 3.10.0 -- python -V # uses mise-provided python"###
)
)]
pub(super) struct SyncPython {
#[usage(long)]
pyenv: bool,
#[usage(long)]
uv: bool,
}
impl SyncPython {
pub(super) async fn run(self) -> Result<()> {
let python = backend::get(&"python".into()).unwrap();
let mut providers = vec![];
if self.pyenv {
providers.push(self.pyenv_links()?);
}
if self.uv {
providers.push(self.uv_links()?);
}
let mut changed = reconcile::reconcile_all(python.ba(), providers)?.into_iter();
if self.pyenv {
for v in changed.next().unwrap_or_default() {
miseprintln!("Synced python@{} from pyenv", v);
}
}
if self.uv {
for v in changed.next().unwrap_or_default() {
miseprintln!("Synced python@{v} from uv to mise");
}
self.sync_mise_installs_to_uv()?;
}
let config = Config::get().await?;
let ts = config.get_toolset().await?;
config::rebuild_shims_and_runtime_symlinks(
&config,
ts,
&[],
crate::lockfile::LockfileUpdateMode::Normal,
)
.await?;
Ok(())
}
fn pyenv_links(&self) -> Result<reconcile::ProviderLinks> {
let pyenv_versions_path = PYENV_ROOT.join("versions");
let subdirs = file::dir_subdirs(&pyenv_versions_path)?;
let mut links = vec![];
for v in sorted(subdirs) {
if v.starts_with(".") {
continue;
}
links.push((v.clone(), pyenv_versions_path.join(&v)));
}
let ownership = reconcile::LinkOwnership::in_namespace(&pyenv_versions_path);
Ok(reconcile::ProviderLinks::new(ownership, links))
}
fn uv_links(&self) -> Result<reconcile::ProviderLinks> {
let uv_versions_path = &*env::UV_PYTHON_INSTALL_DIR;
let subdirs = file::dir_subdirs(uv_versions_path)?;
let mut links = vec![];
for name in sorted(subdirs) {
if name.starts_with(".") {
continue;
}
let Some(v) = name.split('-').nth(1).filter(|v| !v.is_empty()) else {
debug!("skipping unrecognized uv python dir: {name}");
continue;
};
links.push((v.to_string(), uv_versions_path.join(&name)));
}
let ownership = reconcile::LinkOwnership::in_namespace(uv_versions_path);
Ok(reconcile::ProviderLinks::new(ownership, links))
}
fn sync_mise_installs_to_uv(&self) -> Result<()> {
let uv_versions_path = &*env::UV_PYTHON_INSTALL_DIR;
let installed_python_versions_path = dirs::INSTALLS.join("python");
let subdirs = file::dir_subdirs(&installed_python_versions_path)?;
for v in sorted(subdirs) {
if v.starts_with(".") {
continue;
}
let src = installed_python_versions_path.join(&v);
if file::is_symlink_or_junction(&src) {
continue;
}
let os = OS;
let arch = if cfg!(target_arch = "x86_64") {
"x86_64-gnu"
} else if cfg!(target_arch = "aarch64") {
"aarch64-none"
} else {
ARCH
};
let dst = uv_versions_path.join(format!("cpython-{v}-{os}-{arch}"));
if !dst.exists() {
if !uv_versions_path.exists() {
file::create_dir_all(uv_versions_path)?;
}
file::clone_dir(&src, &dst)?;
miseprintln!("Synced python@{v} from mise to uv");
}
}
Ok(())
}
}