Skip to content

Commit

Permalink
Support building with non-git repositories (#95)
Browse files Browse the repository at this point in the history
* add SourceTree enum in place of all direct git::Repository uses

* add SourceTree::Plain variant

* move SdkOrigin into espidf

* update is_managed_sdk docs to reflect current semantics

* add SourceTree::open fn

* make IDF_PATH_VAR relative to workspace in try_from_env

* update according to feedback

* fix clippy error

* cargo fmt

* fix lint issues

* split EspIdf::try_from_env into two functions

---------

Co-authored-by: Hailey Somerville <[email protected]>
Co-authored-by: Alex Martens <[email protected]>
  • Loading branch information
3 people authored Dec 22, 2024
1 parent 480cf6d commit 051d7ce
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 65 deletions.
129 changes: 88 additions & 41 deletions src/espidf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<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,
}

#[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"))
})?;
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<EspIdf, NotActivatedError> {
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,
}
};
Expand All @@ -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!(
Expand All @@ -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,
Expand All @@ -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<EspIdf, FromEnvError> {
// 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.
Expand All @@ -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<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 @@ -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;
Expand All @@ -546,7 +594,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 @@ -563,7 +611,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 @@ -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.

Expand All @@ -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
Expand Down Expand Up @@ -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();
Expand All @@ -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(),
Expand All @@ -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()?;
}

Expand Down Expand Up @@ -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,
Expand Down
24 changes: 0 additions & 24 deletions src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 051d7ce

Please sign in to comment.