Skip to content

Commit

Permalink
feat: added mise sync python --uv (#3575)
Browse files Browse the repository at this point in the history
  • Loading branch information
jdx authored Dec 15, 2024
1 parent ec3853f commit 30d9e19
Show file tree
Hide file tree
Showing 12 changed files with 155 additions and 28 deletions.
2 changes: 1 addition & 1 deletion docs/cli/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ Show extra output (use -vv for even more)
- [`mise shell [FLAGS] [TOOL@VERSION]...`](/cli/shell.md)
- [`mise sync <SUBCOMMAND>`](/cli/sync.md)
- [`mise sync node [FLAGS]`](/cli/sync/node.md)
- [`mise sync python <--pyenv>`](/cli/sync/python.md)
- [`mise sync python [--pyenv] [--uv]`](/cli/sync/python.md)
- [`mise tasks [FLAGS] [TASK] <SUBCOMMAND>`](/cli/tasks.md)
- [`mise tasks deps [--hidden] [--dot] [TASKS]...`](/cli/tasks/deps.md)
- [`mise tasks edit [-p --path] <TASK>`](/cli/tasks/edit.md)
Expand Down
4 changes: 2 additions & 2 deletions docs/cli/sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
- **Usage**: `mise sync <SUBCOMMAND>`
- **Source code**: [`src/cli/sync.rs`](https://github.com/jdx/mise/blob/main/src/cli/sync.rs)

Add tool versions from external tools to mise
Synchronize tools from other version managers with mise

## Subcommands

- [`mise sync node [FLAGS]`](/cli/sync/node.md)
- [`mise sync python <--pyenv>`](/cli/sync/python.md)
- [`mise sync python [--pyenv] [--uv]`](/cli/sync/python.md)
2 changes: 2 additions & 0 deletions docs/cli/sync/node.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Symlinks all tool versions from an external tool into mise

For example, use this to import all Homebrew node installs into mise

This won't overwrite any existing installs but will overwrite any existing symlinks

## Flags

### `--brew`
Expand Down
14 changes: 13 additions & 1 deletion docs/cli/sync/python.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
# `mise sync python`

- **Usage**: `mise sync python <--pyenv>`
- **Usage**: `mise sync python [--pyenv] [--uv]`
- **Source code**: [`src/cli/sync/python.rs`](https://github.com/jdx/mise/blob/main/src/cli/sync/python.rs)

Symlinks all tool versions from an external tool into mise

For example, use this to import all pyenv installs into mise

This won't overwrite any existing installs but will overwrite any existing symlinks

## Flags

### `--pyenv`

Get tool versions from pyenv

### `--uv`

Sync tool versions with uv (2-way sync)

Examples:

```
pyenv install 3.11.0
mise sync python --pyenv
mise use -g python@3.11.0 - uses pyenv-provided python
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
```
2 changes: 1 addition & 1 deletion man/man1/mise.1
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ mise\-shell(1)
Sets a tool version for the current session.
.TP
mise\-sync(1)
Add tool versions from external tools to mise
Synchronize tools from other version managers with mise
.TP
mise\-tasks(1)
Manage tasks
Expand Down
19 changes: 15 additions & 4 deletions mise.usage.kdl
Original file line number Diff line number Diff line change
Expand Up @@ -1199,11 +1199,13 @@ such as `MISE_NODE_VERSION=20` which is "eval"ed as a shell function created by
flag "-u --unset" help="Removes a previously set version"
arg "[TOOL@VERSION]..." help="Tool(s) to use" var=true
}
cmd "sync" subcommand_required=true help="Add tool versions from external tools to mise" {
cmd "sync" subcommand_required=true help="Synchronize tools from other version managers with mise" {
cmd "node" help="Symlinks all tool versions from an external tool into mise" {
long_help r"Symlinks all tool versions from an external tool into mise

For example, use this to import all Homebrew node installs into mise"
For example, use this to import all Homebrew node installs into mise

This won't overwrite any existing installs but will overwrite any existing symlinks"
after_long_help r"Examples:

$ brew install node@18 node@20
Expand All @@ -1217,14 +1219,23 @@ For example, use this to import all Homebrew node installs into mise"
cmd "python" help="Symlinks all tool versions from an external tool into mise" {
long_help r"Symlinks all tool versions from an external tool into mise

For example, use this to import all pyenv installs into mise"
For example, use this to import all pyenv installs into mise

This won't overwrite any existing installs but will overwrite any existing symlinks"
after_long_help r"Examples:

$ pyenv install 3.11.0
$ mise sync python --pyenv
$ mise use -g python@3.11.0 - uses pyenv-provided python

$ 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
"
flag "--pyenv" help="Get tool versions from pyenv" required=true
flag "--pyenv" help="Get tool versions from pyenv"
flag "--uv" help="Sync tool versions with uv (2-way sync)"
}
}
cmd "tasks" help="Manage tasks" {
Expand Down
6 changes: 1 addition & 5 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,11 +293,7 @@ pub trait Backend: Debug + Send + Sync {
_ => None,
}
}
fn create_symlink(
&self,
version: &str,
target: &Path,
) -> eyre::Result<Option<(PathBuf, PathBuf)>> {
fn create_symlink(&self, version: &str, target: &Path) -> Result<Option<(PathBuf, PathBuf)>> {
let link = self.ba().installs_path.join(version);
if link.exists() {
return Ok(None);
Expand Down
2 changes: 1 addition & 1 deletion src/cli/sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ mod node;
mod python;

#[derive(Debug, clap::Args)]
#[clap(about = "Add tool versions from external tools to mise")]
#[clap(about = "Synchronize tools from other version managers with mise")]
pub struct Sync {
#[clap(subcommand)]
command: Commands,
Expand Down
36 changes: 27 additions & 9 deletions src/cli/sync/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use crate::{backend, cmd, config, dirs, file};
/// Symlinks all tool versions from an external tool into mise
///
/// For example, use this to import all Homebrew node installs into mise
///
/// This won't overwrite any existing installs but will overwrite any existing symlinks
#[derive(Debug, clap::Args)]
#[clap(verbatim_doc_comment, after_long_help = AFTER_LONG_HELP)]
pub struct SyncNode {
Expand Down Expand Up @@ -36,15 +38,17 @@ impl SyncNode {
pub fn run(self) -> Result<()> {
if self._type.brew {
self.run_brew()?;
} else if self._type.nvm {
}
if self._type.nvm {
self.run_nvm()?;
} else if self._type.nodenv {
}
if self._type.nodenv {
self.run_nodenv()?;
}
Ok(())
}

fn run_brew(self) -> Result<()> {
fn run_brew(&self) -> Result<()> {
let node = backend::get(&"node".into()).unwrap();

let brew_prefix = PathBuf::from(cmd!("brew", "--prefix").read()?).join("opt");
Expand All @@ -54,18 +58,22 @@ impl SyncNode {

let subdirs = file::dir_subdirs(&brew_prefix)?;
for entry in sorted(subdirs) {
if entry.starts_with(".") {
continue;
}
if !entry.starts_with("node@") {
continue;
}
let v = entry.trim_start_matches("node@");
node.create_symlink(v, &brew_prefix.join(&entry))?;
miseprintln!("Synced node@{} from Homebrew", v);
if node.create_symlink(v, &brew_prefix.join(&entry))?.is_some() {
miseprintln!("Synced node@{} from Homebrew", v);
}
}

config::rebuild_shims_and_runtime_symlinks(&[])
}

fn run_nvm(self) -> Result<()> {
fn run_nvm(&self) -> Result<()> {
let node = backend::get(&"node".into()).unwrap();

let nvm_versions_path = NVM_DIR.join("versions").join("node");
Expand All @@ -80,6 +88,9 @@ impl SyncNode {
let mut created = vec![];
let subdirs = file::dir_subdirs(&nvm_versions_path)?;
for entry in sorted(subdirs) {
if entry.starts_with(".") {
continue;
}
let v = entry.trim_start_matches('v');
let symlink = node.create_symlink(v, &nvm_versions_path.join(&entry))?;
if let Some(symlink) = symlink {
Expand All @@ -96,7 +107,7 @@ impl SyncNode {
config::rebuild_shims_and_runtime_symlinks(&[])
}

fn run_nodenv(self) -> Result<()> {
fn run_nodenv(&self) -> Result<()> {
let node = backend::get(&"node".into()).unwrap();

let nodenv_versions_path = NODENV_ROOT.join("versions");
Expand All @@ -106,8 +117,15 @@ impl SyncNode {

let subdirs = file::dir_subdirs(&nodenv_versions_path)?;
for v in sorted(subdirs) {
node.create_symlink(&v, &nodenv_versions_path.join(&v))?;
miseprintln!("Synced node@{} from nodenv", v);
if v.starts_with(".") {
continue;
}
if node
.create_symlink(&v, &nodenv_versions_path.join(&v))?
.is_some()
{
miseprintln!("Synced node@{} from nodenv", v);
}
}

config::rebuild_shims_and_runtime_symlinks(&[])
Expand Down
84 changes: 81 additions & 3 deletions src/cli/sync/python.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,39 @@
use eyre::Result;
use itertools::sorted;
use std::env::consts::{ARCH, OS};

use crate::env::PYENV_ROOT;
use crate::{backend, config, dirs, file};
use crate::{backend, config, dirs, env, file};

/// Symlinks all tool versions from an external tool into mise
///
/// For example, use this to import all pyenv installs into mise
///
/// This won't overwrite any existing installs but will overwrite any existing symlinks
#[derive(Debug, clap::Args)]
#[clap(verbatim_doc_comment, after_long_help = AFTER_LONG_HELP)]
pub struct SyncPython {
/// Get tool versions from pyenv
#[clap(long, required = true)]
#[clap(long)]
pyenv: bool,

/// Sync tool versions with uv (2-way sync)
#[clap(long)]
uv: bool,
}

impl SyncPython {
pub fn run(self) -> Result<()> {
if self.pyenv {
self.pyenv()?;
}
if self.uv {
self.uv()?;
}
config::rebuild_shims_and_runtime_symlinks(&[])
}

fn pyenv(&self) -> Result<()> {
let python = backend::get(&"python".into()).unwrap();

let pyenv_versions_path = PYENV_ROOT.join("versions");
Expand All @@ -29,11 +46,66 @@ impl SyncPython {

let subdirs = file::dir_subdirs(&pyenv_versions_path)?;
for v in sorted(subdirs) {
if v.starts_with(".") {
continue;
}
python.create_symlink(&v, &pyenv_versions_path.join(&v))?;
miseprintln!("Synced python@{} from pyenv", v);
}
Ok(())
}

config::rebuild_shims_and_runtime_symlinks(&[])
fn uv(&self) -> Result<()> {
let python = backend::get(&"python".into()).unwrap();
let uv_versions_path = &*env::UV_PYTHON_INSTALL_DIR;
let installed_python_versions_path = dirs::INSTALLS.join("python");

file::remove_symlinks_with_target_prefix(
&installed_python_versions_path,
uv_versions_path,
)?;

let subdirs = file::dir_subdirs(uv_versions_path)?;
for name in sorted(subdirs) {
if name.starts_with(".") {
continue;
}
// name is like cpython-3.13.1-macos-aarch64-none
let v = name.split('-').nth(1).unwrap();
if python
.create_symlink(v, &uv_versions_path.join(&name))?
.is_some()
{
miseprintln!("Synced python@{v} from uv to mise");
}
}

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 src.is_symlink() {
continue;
}
// ~/.local/share/uv/python/cpython-3.10.16-macos-aarch64-none
// ~/.local/share/uv/python/cpython-3.13.0-linux-x86_64-gnu
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() {
file::make_symlink(&src, &dst)?;
miseprintln!("Synced python@{v} from mise to uv");
}
}
Ok(())
}
}

Expand All @@ -43,5 +115,11 @@ static AFTER_LONG_HELP: &str = color_print::cstr!(
$ <bold>pyenv install 3.11.0</bold>
$ <bold>mise sync python --pyenv</bold>
$ <bold>mise use -g python@3.11.0</bold> - uses pyenv-provided python
$ <bold>uv python install 3.11.0</bold>
$ <bold>mise install python@3.10.0</bold>
$ <bold>mise sync python --uv</bold>
$ <bold>mise x python@3.11.0 -- python -V</bold> - uses uv-provided python
$ <bold>uv run -p 3.10.0 -- python -V</bold> - uses mise-provided python
"#
);
3 changes: 3 additions & 0 deletions src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ pub static CLICOLOR: Lazy<Option<bool>> = Lazy::new(|| {
// python
pub static PYENV_ROOT: Lazy<PathBuf> =
Lazy::new(|| var_path("PYENV_ROOT").unwrap_or_else(|| HOME.join(".pyenv")));
pub static UV_PYTHON_INSTALL_DIR: Lazy<PathBuf> = Lazy::new(|| {
var_path("UV_PYTHON_INSTALL_DIR").unwrap_or_else(|| XDG_DATA_HOME.join("uv").join("python"))
});

// node
pub static MISE_NODE_CONCURRENCY: Lazy<Option<usize>> = Lazy::new(|| {
Expand Down
9 changes: 8 additions & 1 deletion xtasks/fig/src/mise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2298,7 +2298,7 @@ const completionSpec: Fig.Spec = {
"name": [
"sync"
],
"description": "Add tool versions from external tools to mise",
"description": "Synchronize tools from other version managers with mise",
"subcommands": [
{
"name": [
Expand Down Expand Up @@ -2341,6 +2341,13 @@ const completionSpec: Fig.Spec = {
],
"description": "Get tool versions from pyenv",
"isRepeatable": false
},
{
"name": [
"--uv"
],
"description": "Sync tool versions with uv (2-way sync)",
"isRepeatable": false
}
]
}
Expand Down

0 comments on commit 30d9e19

Please sign in to comment.