diff --git a/Cargo.lock b/Cargo.lock index a6afb33b..c2901b09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -539,7 +539,7 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "huak" -version = "0.0.19" +version = "0.0.19-alpha1" dependencies = [ "clap", "clap_complete", diff --git a/crates/huak-cli/Cargo.toml b/crates/huak-cli/Cargo.toml index d0208190..fae9c567 100644 --- a/crates/huak-cli/Cargo.toml +++ b/crates/huak-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "huak" -version = "0.0.19" +version = "0.0.19-alpha1" description = "A Python package manager written in Rust and inspired by Cargo." repository = "https://github.com/cnpryer/huak.git" homepage = "https://github.com/cnpryer/huak.git" diff --git a/crates/huak-cli/tests/snapshots/r#mod__tests__help-2.snap b/crates/huak-cli/tests/snapshots/r#mod__tests__help-2.snap index eea14569..3a45e768 100644 --- a/crates/huak-cli/tests/snapshots/r#mod__tests__help-2.snap +++ b/crates/huak-cli/tests/snapshots/r#mod__tests__help-2.snap @@ -1,5 +1,5 @@ --- -source: crates/huak_cli/tests/mod.rs +source: crates/huak-cli/tests/mod.rs info: program: huak args: @@ -29,6 +29,7 @@ Commands: remove Remove dependencies from the project run Run a command within the project's environment context test Test the project's Python code + toolchain Manage toolchains update Update the project's dependencies version Display the version of the project help Print this message or the help of the given subcommand(s) diff --git a/crates/huak-cli/tests/snapshots/r#mod__tests__help.snap b/crates/huak-cli/tests/snapshots/r#mod__tests__help.snap index 2961c953..2a4c0d28 100644 --- a/crates/huak-cli/tests/snapshots/r#mod__tests__help.snap +++ b/crates/huak-cli/tests/snapshots/r#mod__tests__help.snap @@ -1,5 +1,5 @@ --- -source: crates/huak_cli/tests/mod.rs +source: crates/huak-cli/tests/mod.rs info: program: huak args: @@ -29,6 +29,7 @@ Commands: remove Remove dependencies from the project run Run a command within the project's environment context test Test the project's Python code + toolchain Manage toolchains update Update the project's dependencies version Display the version of the project help Print this message or the help of the given subcommand(s) diff --git a/crates/huak-package-manager/src/error.rs b/crates/huak-package-manager/src/error.rs index af4cac34..91634f31 100644 --- a/crates/huak-package-manager/src/error.rs +++ b/crates/huak-package-manager/src/error.rs @@ -25,9 +25,9 @@ pub enum Error { #[error("a problem occurred resolving huak's home directory")] HuakHomeNotFound, #[error("a toolchain cannot be found")] - HuakToolchainNotFound, + ToolchainNotFound, #[error("{0}")] // See TODO note above. - HuakToolchainError(#[from] huak_toolchain::Error), + ToolchainError(#[from] huak_toolchain::Error), #[error("a toolchain already exists: {0}")] LocalToolchainExists(PathBuf), #[error("a problem with huak's internals occurred: {0}")] diff --git a/crates/huak-package-manager/src/ops/toolchain.rs b/crates/huak-package-manager/src/ops/toolchain.rs index 03268339..1fd48751 100644 --- a/crates/huak-package-manager/src/ops/toolchain.rs +++ b/crates/huak-package-manager/src/ops/toolchain.rs @@ -282,7 +282,7 @@ fn install(path: PathBuf, channel: Channel, config: &Config) -> HuakResult<()> { .is_err() { if let Err(e) = toolchain.register_tool_from_path(&path, "python", true) { - return Err(Error::HuakToolchainError(e)); + return Err(Error::ToolchainError(e)); } } diff --git a/crates/huak-package-manager/src/workspace.rs b/crates/huak-package-manager/src/workspace.rs index 3e70906a..a99c94f6 100644 --- a/crates/huak-package-manager/src/workspace.rs +++ b/crates/huak-package-manager/src/workspace.rs @@ -128,7 +128,7 @@ impl Workspace { /// 3. ~/.huak/settings.toml configuration pub fn resolve_local_toolchain(&self, channel: Option<&Channel>) -> HuakResult { let Some(it) = resolve_local_toolchain(self, channel) else { - return Err(Error::HuakToolchainNotFound); + return Err(Error::ToolchainNotFound); }; Ok(it) diff --git a/crates/huak-python-manager/src/lib.rs b/crates/huak-python-manager/src/lib.rs index c9d6d755..1e39d9d9 100644 --- a/crates/huak-python-manager/src/lib.rs +++ b/crates/huak-python-manager/src/lib.rs @@ -20,17 +20,13 @@ //! //! ```no_run //! use std::{str::FromStr, path::PathBuf}; -//! use huak_python_manager::{Options, RequestedVersion, Strategy, install_with_target, resolve_release}; -//! -//! -//! // The version of the Python to install. -//! let version = RequestedVersion::from_str("3.12").unwrap(); +//! use huak_python_manager::{ReleaseOptions, Strategy, install_with_target, resolve_release}; //! //! // Target directory to install Python to. //! let target = PathBuf::from("..."); //! //! // Use selection strategy to resolve for the best matching release available. -//! let strategy = Strategy::Selection(Options { version: Some(version), kind: "cpython", os: "apple", architecture: "aarch64", build_configuration: "pgo+lto"}); +//! let strategy = Strategy::Selection(ReleaseOptions::default()); //! //! let release = resolve_release(&strategy).unwrap(); //! @@ -38,7 +34,6 @@ //! ``` pub use crate::error::Error; -pub use crate::install::install_with_target; pub use crate::resolve::{ release_options_from_requested_version, resolve_release, ReleaseArchitecture, ReleaseBuildConfiguration, ReleaseKind, ReleaseOption, ReleaseOptions, ReleaseOs, @@ -46,6 +41,7 @@ pub use crate::resolve::{ }; pub use crate::version::Version; use install::download_release; +pub use install::install_with_target; pub use releases::Release; use std::path::Path; use tar::Archive; diff --git a/crates/huak-toolchain/README.md b/crates/huak-toolchain/README.md index 42c8fdd7..2db557b2 100644 --- a/crates/huak-toolchain/README.md +++ b/crates/huak-toolchain/README.md @@ -1,3 +1,13 @@ # Toolchain -The toolchain implementation for Huak. \ No newline at end of file +The toolchain implementation for Huak. + +This crate is a work-in-progress. + +## Usage + +**This crate is designed to be used by Huak.** Install a toolchain to ~/.huak/toolchains. + +``` +huak toolchain install +``` diff --git a/crates/huak-toolchain/src/install.rs b/crates/huak-toolchain/src/install.rs deleted file mode 100644 index 8dfce361..00000000 --- a/crates/huak-toolchain/src/install.rs +++ /dev/null @@ -1,170 +0,0 @@ -use crate::{channel::DescriptorParts, Channel, Error, LocalToolchain}; -use huak_python_manager::{ - install_with_target as install_python_with_target, release_options_from_requested_version, - resolve_release as resolve_python_release, ReleaseOption, ReleaseOptions, RequestedVersion, - Strategy as PythonStrategy, -}; -#[cfg(unix)] -use std::os::unix::fs::symlink; -#[cfg(windows)] -use std::os::windows::fs::symlink_file; -use std::{ - env::consts::OS, - fs::{self, hard_link, read_link}, - path::{Path, PathBuf}, - str::FromStr, -}; - -pub fn install_toolchain_with_target( - toolchain: &LocalToolchain, - target: &PathBuf, -) -> Result<(), Error> { - if target.exists() { - Err(Error::LocalToolchainExistsError(target.clone())) - } else { - setup_toolchain(toolchain, target) - } -} - -fn setup_toolchain(toolchain: &LocalToolchain, path: &PathBuf) -> Result<(), Error> { - fs::create_dir_all(path)?; - - let downloads = path.join("downloads"); - fs::create_dir_all(&downloads)?; - - let bin = path.join("bin"); - fs::create_dir_all(&bin)?; - - // Get the path to the installed interpreter. - let py_path = maybe_exe( - downloads - .join("python") - .join("install") - .join(py_bin_name()) - .join("python3"), - ); - - install_python(toolchain, &downloads)?; - create_proxy_file(py_path, bin.join("python"))?; - - // TODO(cnpryer): Rest of tools - // todo!() - Ok(()) -} - -fn py_bin_name() -> &'static str { - #[cfg(unix)] - let name = "bin"; - - #[cfg(windows)] - let name = "Scripts"; - - name -} - -fn install_python(toolchain: &LocalToolchain, target: &PathBuf) -> Result<(), Error> { - let strategy = python_strategy_from_channel(toolchain.channel())?; - - let Some(release) = resolve_python_release(&strategy) else { - return Err(Error::PythonInstallationError(format!( - "could not resolve python with {strategy}" - ))); - }; - - Ok(install_python_with_target(&release, target)?) -} - -fn python_strategy_from_channel(channel: &Channel) -> Result { - let options = match channel { - Channel::Default => ReleaseOptions::default(), // TODO(cnpryer): Is there ever a case where channel default doesn't yield python default? - Channel::Version(version) => { - release_options_from_requested_version(RequestedVersion::from(*version))? - } - Channel::Descriptor(desc) => python_options_from_descriptor(desc)?, - }; - - Ok(PythonStrategy::Selection(options)) -} - -fn python_options_from_descriptor(desc: &DescriptorParts) -> Result { - let mut options = ReleaseOptions::default(); - - if let Some(kind) = desc.kind.as_ref() { - options.kind = ReleaseOption::from_str(kind).ok(); - } - - if let Some(version) = desc.version.as_ref() { - options.version = Some(ReleaseOption::from_str(&version.to_string())?); - } - - if let Some(architecture) = desc.architecture.as_ref() { - options.kind = ReleaseOption::from_str(architecture).ok(); - } - - if let Some(build_configuration) = desc.build_configuration.as_ref() { - options.kind = ReleaseOption::from_str(build_configuration).ok(); - } - - Ok(options) -} - -// TODO(cnpryer): -// - More robust support -// - Privileged action on windows https://learn.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/create-symbolic-links -// - Get file metadata for priv check (linux too) -// - Do symlinks not work on linux? -// - https://github.com/cnpryer/huak/issues/809 -// - check if file is already a link, if not attempt to make it one. -fn create_proxy_file>(original: T, link: T) -> Result<(), Error> { - let original = original.as_ref(); - let link = link.as_ref(); - - // If we can read the link we'll just make our own symlink of the original link's linked file - if let Ok(it) = read_link(original) { - // Do our best to get the path linked file - let p = if it.is_absolute() { - it - } else if let Some(parent) = original.parent() { - parent.join(it) - } else { - fs::canonicalize(it)? - }; - - // Attempt to create a symlink. If that doesn't work then we hardlink. If we can't hardlink we copy. - if try_symlink(p.as_path(), link).is_ok() || hard_link(p.as_path(), link).is_ok() { - Ok(()) - } else { - // Last resort is to copy the Python interpreter - println!( - " failed to link {} with {}", - link.display(), - original.display() - ); - println!(" copying {} to {}", original.display(), link.display()); - let _ = fs::copy(p.as_path(), link)?; - Ok(()) - } - } else if try_symlink(original, link).is_ok() || hard_link(original, link).is_ok() { - Ok(()) - } else { - todo!() - } -} - -fn try_symlink>(original: T, link: T) -> Result<(), Error> { - #[cfg(unix)] - let err = symlink(original, link); - - #[cfg(windows)] - let err = symlink_file(original, link); - - Ok(err?) -} - -fn maybe_exe(path: PathBuf) -> PathBuf { - if OS == "windows" && path.extension().map_or(false, |it| it == "exe") { - path.with_extension("exe") - } else { - path - } -} diff --git a/crates/huak-toolchain/src/lib.rs b/crates/huak-toolchain/src/lib.rs index 5df4ef74..b2d62207 100644 --- a/crates/huak-toolchain/src/lib.rs +++ b/crates/huak-toolchain/src/lib.rs @@ -49,14 +49,17 @@ //! Local tools can be executable programs. //! //! ```rust -//! use huak_toolchain::prelude::*; +//! use huak_toolchain::LocalToolchain; +//! use std::path::PathBuf; //! -//! let path = PathBuff::new("path/to/toolchain/"); -//! let toolchain = LocalToolchain::new(path)?; +//! let path = PathBuf::from("path/to/toolchain/"); +//! let toolchain = LocalToolchain::new(&path); //! let py = toolchain.tool("python"); +//! let bin = path.join("bin"); +//! let py_bin = bin.join("python"); //! -//! assert_eq!(py.name, "python"); -//! assert_eq!(py.path, path.join("bin").join("python")) +//! assert_eq!(&py.name, "python"); +//! assert_eq!(py.path, py_bin); //! ``` //! //! Use `toolchain.try_with_proxy_tool(tool)` to attempt to create a proxy file installed to the toolchain. @@ -66,14 +69,15 @@ //! may contain full copies of executable programs or proxies to them. //! //! ``` +//! +//! ``` //! export PATH="/path/to/toolchain/bin/:$PATH" //! ``` pub use channel::{Channel, DescriptorParts}; pub use error::Error; -pub use install::install_toolchain_with_target; use path::name_from_path; -pub use resolve::{Entry, LocalToolchainResolver}; +pub use resolve::LocalToolchainResolver; #[cfg(unix)] use std::os::unix::fs::symlink; #[cfg(windows)] @@ -86,7 +90,6 @@ pub use tools::LocalTool; mod channel; mod error; -mod install; mod path; mod resolve; mod tools; @@ -96,7 +99,25 @@ pub struct LocalToolchain { inner: LocalToolchainInner, } -// TODO(cnpryer): Teardown +/// The local toolchain for Huak. +/// +/// A local toolchain is created for different channels. The channel determines its +/// release installs and its path. +/// +/// A `LocalToolchain` is meant to be used as an API for toolchain management on some +/// filesystem. +/// +/// ```rust +/// use std::path::PathBuf; +/// use huak_toolchain::LocalToolchain; +/// +/// +/// let root = PathBuf::new(); +/// let toolchain = LocalToolchain::new(&root); +/// +/// assert_eq!(toolchain.root(), &root); +/// assert_eq!(toolchain.bin(), root.join("bin")); +/// ``` impl LocalToolchain { pub fn new>(path: T) -> Self { let path = path.into(); diff --git a/crates/huak-toolchain/src/resolve.rs b/crates/huak-toolchain/src/resolve.rs index 2d2ae7ae..e1d94e83 100644 --- a/crates/huak-toolchain/src/resolve.rs +++ b/crates/huak-toolchain/src/resolve.rs @@ -5,6 +5,19 @@ use std::{ use crate::{Channel, LocalToolchain}; +/// A struct used to resolve `LocalToolchain`s from some filesystem. +/// +/// `LocalToolchainResolver`s are used to search for toolchains on the filesystem using +/// `Channel`s. +/// +/// ```rust +/// use std::path::PathBuf; +/// use huak_toolchain::{Channel, LocalToolchainResolver}; +/// +/// let dir = PathBuf::new(); +/// let resolver = LocalToolchainResolver::new(); +/// let toolchain = resolver.from_dir(&Channel::Default, dir); +/// ``` #[derive(Default)] pub struct LocalToolchainResolver; @@ -35,10 +48,6 @@ impl LocalToolchainResolver { } } -pub enum Entry { - String(String), -} - fn resolve_from_dir>(channel: &Channel, path: T) -> Option { let Ok(paths) = fs::read_dir(path.as_ref()) else { return None; diff --git a/crates/huak-toolchain/src/tools.rs b/crates/huak-toolchain/src/tools.rs index c530edbe..d3400c21 100644 --- a/crates/huak-toolchain/src/tools.rs +++ b/crates/huak-toolchain/src/tools.rs @@ -1,6 +1,18 @@ use crate::name_from_path; use std::{fmt::Display, path::PathBuf, str::FromStr}; +/// The local tool for Huak's toolchain system. +/// +/// A `LocalTool` provides a small wrapper for tool paths. +/// ```rust +/// use std::path::PathBuf; +/// use huak_toolchain::LocalTool; +/// +/// let path = PathBuf::new(); +/// let tool = LocalTool::new(&path); +/// +/// assert_eq!(&path, &tool.path); +/// ``` #[derive(Clone, Debug)] pub struct LocalTool { pub name: String, diff --git a/pyproject.toml b/pyproject.toml index 42042103..2fa33466 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "huak" -version = "0.0.19" +version = "0.0.19a1" description = "A Python package manager written in Rust and inspired by Cargo." authors = [ {email = "cnpryer@gmail.com"},