Skip to content

Support building with non-git repositories #95

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

Merged
merged 11 commits into from
Dec 22, 2024
92 changes: 60 additions & 32 deletions src/espidf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,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 @@ -338,32 +338,56 @@ 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,
}

#[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 @@ -388,7 +412,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 @@ -407,15 +431,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 @@ -433,8 +457,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 @@ -507,7 +531,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 @@ -518,7 +547,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 @@ -535,7 +564,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 @@ -583,21 +612,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 @@ -608,14 +636,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 @@ -646,7 +674,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 @@ -655,7 +683,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 @@ -679,7 +707,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 @@ -714,7 +742,7 @@ impl Installer {
log::debug!("Using PATH='{}'", &paths.to_string_lossy());

Ok(EspIdf {
repository,
esp_idf_dir,
exported_path: paths,
venv_python,
version: esp_version,
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