diff --git a/src/espidf.rs b/src/espidf.rs index e092d28..33247e0 100644 --- a/src/espidf.rs +++ b/src/espidf.rs @@ -348,20 +348,31 @@ pub enum FromEnvError { NoRepo(#[source] anyhow::Error), /// An `esp-idf` repository exists but the environment is not activated. #[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, - /// The source error why detection failed. + NotActivated( + #[from] #[source] - source: anyhow::Error, - }, + NotActivatedError, + ), +} + +/// The error returned by [`EspIdf::try_from`]. +/// Indicates that there was some issue during the initial configuration of the +/// `esp-idf` source tree. +#[derive(Debug, thiserror::Error)] +#[error("Error activating `esp-idf` tools")] +pub struct NotActivatedError { + /// The esp-idf repository detected from the environment. + esp_idf_dir: SourceTree, + /// The source error why detection failed. + #[source] + source: anyhow::Error, } /// 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 environment variables of all tools. @@ -370,24 +381,42 @@ pub struct EspIdf { 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")) - })?; - let repo = git::Repository::open(idf_path).map_err(FromEnvError::NoRepo)?; + /// Try to load an activated esp-idf from at the given path. + /// `idf_path`: Path to an existing `esp-idf` source tree. + pub fn try_from(idf_path: &Path) -> Result { + 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(), + let not_activated = |source: Error| -> NotActivatedError { + NotActivatedError { + esp_idf_dir: esp_idf_dir.clone(), source, } }; @@ -412,7 +441,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!( @@ -431,15 +460,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, // Env vars are already set by the parent process // and since it is very difficult to extract these, @@ -449,6 +478,20 @@ impl EspIdf { is_managed_espidf: true, }) } + + /// Try to detect an activated esp-idf environment. + /// Expects `$IDF_PATH` to be contain a path to an `esp-idf` source tree. + pub fn try_from_env() -> Result { + // detect repo from $IDF_PATH if not passed by caller + let idf_path = env::var_os(IDF_PATH_VAR) + .map(PathBuf::from) + .ok_or_else(|| { + FromEnvError::NoRepo(anyhow!("environment variable `{IDF_PATH_VAR}` not found")) + })?; + + let idf = Self::try_from(&idf_path)?; + Ok(idf) + } } /// The version of an esp-idf repository. @@ -461,8 +504,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!( @@ -535,7 +578,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; @@ -546,7 +594,7 @@ pub struct Installer { custom_install_dir: Option, #[allow(clippy::type_complexity)] tools_provider: - Option) -> Result>>>, + Option) -> Result>>>, } impl Installer { @@ -563,7 +611,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 @@ -611,21 +659,20 @@ 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), }; - // Reading the version out of a cmake build file - let esp_version = EspIdfVersion::try_from(&repository)?; + let esp_version = EspIdfVersion::try_from(esp_idf_dir.path())?; // Create python virtualenv or use a previously installed one. @@ -636,14 +683,14 @@ impl Installer { let python_version = python::check_python_at_least(3, 6)?; // Using the idf_tools.py script version that comes with the esp-idf git repository - 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"]; // TODO: add virtual_env check to skip install-python-env // running the command cost 2-3 seconds but always makes sure that everything is installed correctly and is up-to-date // assumes that the command can be run repeatedly // whenalready installed -> checks for updates and a working state - 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()?; // since the above command exited sucessfully -> there should be a virt_env dir @@ -674,7 +721,7 @@ impl Installer { let tools = self .tools_provider - .map(|p| p(&repository, &esp_version)) + .map(|p| p(&esp_idf_dir, &esp_version)) .unwrap_or(Ok(Vec::new()))?; let tools_wanted = tools.clone(); @@ -683,7 +730,7 @@ impl Installer { .flat_map(|tool| tool.tools.iter().map(|s| s.as_str())) .collect(); - let tools_json = repository.worktree().join("tools/tools.json"); + let tools_json = esp_idf_dir.path().join("tools/tools.json"); let tools_vec = parse_tools( tools_wanted.clone(), @@ -707,7 +754,7 @@ impl Installer { .into_iter() .flatten(); - cmd!(&venv_python, &idf_tools_py, "--idf-path", repository.worktree(), @tools_json.clone(), "install"; + cmd!(&venv_python, &idf_tools_py, "--idf-path", esp_idf_dir.path(), @tools_json.clone(), "install"; env=(IDF_TOOLS_PATH_VAR, &install_dir), args=(tool_set.tools)).run()?; } @@ -748,7 +795,7 @@ impl Installer { log::debug!("Using PATH='{}'", &paths.to_string_lossy()); Ok(EspIdf { - repository, + esp_idf_dir, exported_path: paths, exported_env_vars: env_vars, venv_python, diff --git a/src/git.rs b/src/git.rs index 750f292..59f13c7 100644 --- a/src/git.rs +++ b/src/git.rs @@ -505,30 +505,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 {