diff --git a/src/espidf.rs b/src/espidf.rs index f6c5f70..766571d 100644 --- a/src/espidf.rs +++ b/src/espidf.rs @@ -112,7 +112,7 @@ pub enum FromEnvError { #[error("`esp-idf` repository exists but required tools not in environment")] NotActivated { /// The esp-idf repository detected from the environment. - esp_idf_repo: git::Repository, + esp_idf_dir: SourceTree, /// The source error why detection failed. #[source] source: anyhow::Error, @@ -122,32 +122,58 @@ pub enum FromEnvError { /// Information about a esp-idf source and tools installation. #[derive(Debug)] pub struct EspIdf { - /// The esp-idf repository. - pub repository: git::Repository, + /// The esp-idf source tree. + pub esp_idf_dir: SourceTree, /// The binary paths of all tools concatenated with the system `PATH` env variable. pub exported_path: OsString, /// The path to the python executable to be used by the esp-idf. pub venv_python: PathBuf, /// The version of the esp-idf or [`Err`] if it could not be detected. pub version: Result, - /// Whether [`EspIdf::repository`] is installed and managed by [`Installer`] and - /// **not** provided by the user. + /// Whether [`EspIdf::tree`] is a repository installed and managed by + /// [`Installer`] and **not** provided by the user. pub is_managed_espidf: bool, } +#[derive(Debug, Clone)] +pub enum SourceTree { + Git(git::Repository), + Plain(PathBuf), +} + +impl SourceTree { + pub fn open(path: &Path) -> Self { + git::Repository::open(path) + .map(SourceTree::Git) + .unwrap_or_else(|_| SourceTree::Plain(path.to_owned())) + } + + pub fn path(&self) -> &Path { + match self { + SourceTree::Git(repo) => repo.worktree(), + SourceTree::Plain(path) => path, + } + } +} + impl EspIdf { /// Try to detect an activated esp-idf environment. - pub fn try_from_env() -> Result { - // detect repo from $IDF_PATH - let idf_path = env::var_os(IDF_PATH_VAR).ok_or_else(|| { - FromEnvError::NoRepo(anyhow!("environment variable `{IDF_PATH_VAR}` not found")) + pub fn try_from_env(idf_path: Option<&Path>) -> Result { + let idf_path = idf_path.map(Path::to_owned).ok_or(()).or_else(|()| { + // detect repo from $IDF_PATH if not passed by caller + env::var_os(IDF_PATH_VAR) + .map(PathBuf::from) + .ok_or_else(|| { + FromEnvError::NoRepo(anyhow!("environment variable `{IDF_PATH_VAR}` not found")) + }) })?; - let repo = git::Repository::open(idf_path).map_err(FromEnvError::NoRepo)?; + + let esp_idf_dir = SourceTree::open(&idf_path); let path_var = env::var_os("PATH").unwrap_or_default(); let not_activated = |source: Error| -> FromEnvError { FromEnvError::NotActivated { - esp_idf_repo: repo.clone(), + esp_idf_dir: esp_idf_dir.clone(), source, } }; @@ -172,7 +198,7 @@ impl EspIdf { .map_err(not_activated)?; // make sure ${IDF_PATH}/tools/idf.py matches idf.py in $PATH - let idf_py_repo = path_buf![repo.worktree(), "tools", "idf.py"]; + let idf_py_repo = path_buf![esp_idf_dir.path(), "tools", "idf.py"]; match (idf_py.canonicalize(), idf_py_repo.canonicalize()) { (Ok(a), Ok(b)) if a != b => { return Err(not_activated(anyhow!( @@ -191,15 +217,15 @@ impl EspIdf { .with_context(|| anyhow!("python not found in $PATH")) .map_err(not_activated)?; let check_python_deps_py = - path_buf![repo.worktree(), "tools", "check_python_dependencies.py"]; + path_buf![esp_idf_dir.path(), "tools", "check_python_dependencies.py"]; cmd!(&python, &check_python_deps_py) .stdout() .with_context(|| anyhow!("failed to check python dependencies")) .map_err(not_activated)?; Ok(EspIdf { - version: EspIdfVersion::try_from(&repo), - repository: repo, + version: EspIdfVersion::try_from(esp_idf_dir.path()), + esp_idf_dir, exported_path: path_var, venv_python: python, is_managed_espidf: true, @@ -217,8 +243,8 @@ pub struct EspIdfVersion { impl EspIdfVersion { /// Try to extract the esp-idf version from an actual cloned repository. - pub fn try_from(repo: &git::Repository) -> Result { - let version_cmake = path_buf![repo.worktree(), "tools", "cmake", "version.cmake"]; + pub fn try_from(esp_idf_dir: &Path) -> Result { + let version_cmake = path_buf![esp_idf_dir, "tools", "cmake", "version.cmake"]; let base_err = || { anyhow!( @@ -291,7 +317,12 @@ impl std::fmt::Display for EspIdfVersion { /// - [`EspIdfOrigin::Custom`] values are designating a user-provided, already cloned /// ESP-IDF repository which lives outisde the [`Installer`]'s installation directory. It is /// only read by the [`Installer`] so as to install the required tooling. -pub type EspIdfOrigin = git::sdk::SdkOrigin; +pub enum EspIdfOrigin { + /// The [`Installer`] will install and manage the SDK. + Managed(git::sdk::RemoteSdk), + /// User-provided SDK repository untouched by the [`Installer`]. + Custom(SourceTree), +} /// A distinct version of the esp-idf repository to be installed. pub type EspIdfRemote = git::sdk::RemoteSdk; @@ -302,7 +333,7 @@ pub struct Installer { custom_install_dir: Option, #[allow(clippy::type_complexity)] tools_provider: - Option) -> Result>>>, + Option) -> Result>>>, } impl Installer { @@ -319,7 +350,7 @@ impl Installer { #[must_use] pub fn with_tools(mut self, provider: F) -> Self where - F: 'static + FnOnce(&git::Repository, &Result) -> Result>, + F: 'static + FnOnce(&SourceTree, &Result) -> Result>, { self.tools_provider = Some(Box::new(provider)); self @@ -367,19 +398,19 @@ impl Installer { ) })?; - let (repository, managed_repo) = match self.esp_idf_origin { + let (esp_idf_dir, managed_repo) = match self.esp_idf_origin { EspIdfOrigin::Managed(managed) => ( - managed.open_or_clone( + SourceTree::Git(managed.open_or_clone( &install_dir, git::CloneOptions::new().depth(1), DEFAULT_ESP_IDF_REPOSITORY, MANAGED_ESP_IDF_REPOS_DIR_BASE, - )?, + )?), true, ), - EspIdfOrigin::Custom(repository) => (repository, false), + EspIdfOrigin::Custom(tree) => (tree, false), }; - let version = EspIdfVersion::try_from(&repository); + let version = EspIdfVersion::try_from(esp_idf_dir.path()); let path_var_sep = if cfg!(windows) { ';' } else { ':' }; @@ -388,10 +419,10 @@ impl Installer { // TODO: also install python python::check_python_at_least(3, 6)?; - let idf_tools_py = path_buf![repository.worktree(), "tools", "idf_tools.py"]; + let idf_tools_py = path_buf![esp_idf_dir.path(), "tools", "idf_tools.py"]; let get_python_env_dir = || -> Result { - cmd!(PYTHON, &idf_tools_py, "--idf-path", repository.worktree(), "--quiet", "export", "--format=key-value"; + cmd!(PYTHON, &idf_tools_py, "--idf-path", esp_idf_dir.path(), "--quiet", "export", "--format=key-value"; ignore_exitcode=(), env=(IDF_TOOLS_PATH_VAR, &install_dir), env_remove=(IDF_PYTHON_ENV_PATH_VAR), env_remove=("MSYSTEM")) .stdout()? @@ -408,7 +439,7 @@ impl Installer { let python_env_dir: PathBuf = match get_python_env_dir() { Ok(dir) if Path::new(&dir).exists() => dir, _ => { - cmd!(PYTHON, &idf_tools_py, "--idf-path", repository.worktree(), "--non-interactive", "install-python-env"; + cmd!(PYTHON, &idf_tools_py, "--idf-path", esp_idf_dir.path(), "--non-interactive", "install-python-env"; env=(IDF_TOOLS_PATH_VAR, &install_dir), env_remove=("MSYSTEM"), env_remove=(IDF_PYTHON_ENV_PATH_VAR)).run()?; get_python_env_dir()? } @@ -427,7 +458,7 @@ impl Installer { // Install tools. let tools = self .tools_provider - .map(|p| p(&repository, &version)) + .map(|p| p(&esp_idf_dir, &version)) .unwrap_or(Ok(Vec::new()))?; let mut exported_paths = HashSet::new(); for tool in tools { @@ -439,7 +470,7 @@ impl Installer { .flatten(); // Install the tools. - cmd!(&python, &idf_tools_py, "--idf-path", repository.worktree(), @tools_json.clone(), "install"; + cmd!(&python, &idf_tools_py, "--idf-path", esp_idf_dir.path(), @tools_json.clone(), "install"; env=(IDF_TOOLS_PATH_VAR, &install_dir), args=(tool.tools)).run()?; // Get the paths to the tools. @@ -452,7 +483,7 @@ impl Installer { // native paths in rust. So we remove that environment variable when calling // idf_tools.py. exported_paths.extend( - cmd!(&python, &idf_tools_py, "--idf-path", repository.worktree(), @tools_json, "--quiet", "export", "--format=key-value"; + cmd!(&python, &idf_tools_py, "--idf-path", esp_idf_dir.path(), @tools_json, "--quiet", "export", "--format=key-value"; ignore_exitcode=(), env=(IDF_TOOLS_PATH_VAR, &install_dir), env_remove=("MSYSTEM")).stdout()? .lines() .find(|s| s.trim_start().starts_with("PATH=")) @@ -474,7 +505,7 @@ impl Installer { log::debug!("Using PATH='{}'", &paths.to_string_lossy()); Ok(EspIdf { - repository, + esp_idf_dir, exported_path: paths, venv_python: python, version, diff --git a/src/git.rs b/src/git.rs index 20b813a..aa34482 100644 --- a/src/git.rs +++ b/src/git.rs @@ -495,30 +495,6 @@ pub mod sdk { use crate::git; - /// The origin of the SDK repository. - /// - /// Two variations exist: - /// - Managed - /// The SDK source is installed automatically. - /// - Custom - /// A user-provided local clone the SDK repository. - /// - /// In both cases the [`Installer`] will install all required tools. - /// - /// The main difference between managed and custom SDK origin is reflected in their naming: - /// - [`SdkOrigin::Managed`] values are cloned locally by the [`Installer`] instance, inside its tooling installation directory. - /// Consenquently, these SDK repository clones will disappar if the installation directory is deleted by the user. - /// - [`SdkOrigin::Custom`] values are designating a user-provided, already cloned - /// SDK repository which lives outisde the [`Installer`]'s installation directory. It is - /// only read by the [`Installer`] so as to install the required tooling. - #[derive(Debug, Clone)] - pub enum SdkOrigin { - /// The [`Installer`] will install and manage the SDK. - Managed(RemoteSdk), - /// User-provided SDK repository untouched by the [`Installer`]. - Custom(git::Repository), - } - /// A distinct version of the SDK repository to be installed. #[derive(Debug, Clone)] pub struct RemoteSdk {