Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support esp-idf source tree not being Git repository #80

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 63 additions & 32 deletions src/espidf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<EspIdfVersion>,
/// 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,
haileys marked this conversation as resolved.
Show resolved Hide resolved
}

#[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<EspIdf, FromEnvError> {
// 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<EspIdf, FromEnvError> {
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,
}
};
Expand All @@ -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!(
Expand All @@ -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,
Expand All @@ -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<Self> {
let version_cmake = path_buf![repo.worktree(), "tools", "cmake", "version.cmake"];
pub fn try_from(esp_idf_dir: &Path) -> Result<Self> {
let version_cmake = path_buf![esp_idf_dir, "tools", "cmake", "version.cmake"];

let base_err = || {
anyhow!(
Expand Down Expand Up @@ -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;
Expand All @@ -302,7 +333,7 @@ pub struct Installer {
custom_install_dir: Option<PathBuf>,
#[allow(clippy::type_complexity)]
tools_provider:
Option<Box<dyn FnOnce(&git::Repository, &Result<EspIdfVersion>) -> Result<Vec<Tools>>>>,
Option<Box<dyn FnOnce(&SourceTree, &Result<EspIdfVersion>) -> Result<Vec<Tools>>>>,
}

impl Installer {
Expand All @@ -319,7 +350,7 @@ impl Installer {
#[must_use]
pub fn with_tools<F>(mut self, provider: F) -> Self
where
F: 'static + FnOnce(&git::Repository, &Result<EspIdfVersion>) -> Result<Vec<Tools>>,
F: 'static + FnOnce(&SourceTree, &Result<EspIdfVersion>) -> Result<Vec<Tools>>,
{
self.tools_provider = Some(Box::new(provider));
self
Expand Down Expand Up @@ -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 { ':' };

Expand All @@ -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<String> {
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()?
Expand All @@ -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()?
}
Expand All @@ -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 {
Expand All @@ -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.
Expand All @@ -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="))
Expand All @@ -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,
Expand Down
24 changes: 0 additions & 24 deletions src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading