Skip to content

Commit

Permalink
Provide tools exported env vars; do not assume that each installed to…
Browse files Browse the repository at this point in the history
…ol is an executable binary
  • Loading branch information
ivmarkov committed Dec 22, 2024
1 parent 0e49675 commit 480cf6d
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 45 deletions.
93 changes: 64 additions & 29 deletions src/espidf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
//!
//! - **`~/.espressif`**, if `install_dir` is None
use std::collections::HashMap;
use std::ffi::{OsStr, OsString};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -125,9 +126,14 @@ struct Tool {
install_dir: PathBuf,
/// Path relative to install dir
export_path: PathBuf,
/// Environment variables to export for that tool
export_vars: HashMap<String, String>,
/// Command path and args that printout the current version of the tool
/// - First element is the relative path to the command
/// - Every other element represents a arg given to the cmd
/// - Every other element represents an arg given to the cmd
///
/// Note that if the args are with length 1 and the first element is empty
/// then the tool is not really a binary tool and does not have a version command
version_cmd_args: Vec<String>,
/// regex to extract the version returned by the version_cmd
version_regex: String,
Expand All @@ -142,46 +148,61 @@ impl Tool {
if !tool_path.exists() {
return false;
}
log::debug!(
"Run cmd: {:?} to get current tool version",
self.test_command(),
);

let output = self.test_command().output().unwrap_or_else(|e| {
panic!(
"Failed to run command: {:?}; error: {e:?}",
self.test_command()
)
});

let regex = regex::Regex::new(&self.version_regex).expect("Invalid regex pattern provided");
if let Some(mut test_command) = self.test_command() {
log::debug!("Run cmd: {test_command:?} to get current tool version");

let output = test_command.output().unwrap_or_else(|e| {
panic!("Failed to run command: {test_command:?}; error: {e:?}")
});

if !self.version_regex.is_empty() {
let regex =
regex::Regex::new(&self.version_regex).expect("Invalid regex pattern provided");

if let Some(capture) = regex.captures(&String::from_utf8_lossy(&output.stdout)) {
if let Some(var) = capture.get(0) {
log::debug!("Match: {:?}, Version: {:?}", &var.as_str(), &self.version);
return true;
if let Some(capture) = regex.captures(&String::from_utf8_lossy(&output.stdout)) {
if let Some(var) = capture.get(0) {
log::debug!("Match: {:?}, Version: {:?}", &var.as_str(), &self.version);
return true;
}
}

false
} else {
true
}
} else {
true
}

false
}

/// get the absolute PATH
fn abs_export_path(&self) -> PathBuf {
self.install_dir.join(self.export_path.as_path())
}

/// Return the exported env vars with the special `${TOOL_PATH}` value
/// substituted with the tool path
fn abs_export_env_vars(&self) -> impl Iterator<Item = (String, String)> + '_ {
self.export_vars.iter().map(|(var, value)| {
let value = value.replace("${TOOL_PATH}", self.abs_export_path().to_str().unwrap());
(var.clone(), value)
})
}

/// Creates a Command that will echo back the current version of the tool
///
/// Since Command is non clonable this helper is provided
fn test_command(&self) -> Command {
let cmd_abs_path = self
.abs_export_path()
.join(self.version_cmd_args[0].clone());

let mut version_cmd = std::process::Command::new(cmd_abs_path);
version_cmd.args(self.version_cmd_args[1..].iter().cloned());
version_cmd
fn test_command(&self) -> Option<Command> {
(!self.version_cmd_args.is_empty() && !self.version_cmd_args[0].is_empty()).then(|| {
let cmd_abs_path = self
.abs_export_path()
.join(self.version_cmd_args[0].clone());

let mut version_cmd = std::process::Command::new(cmd_abs_path);
version_cmd.args(self.version_cmd_args[1..].iter().cloned());
version_cmd
})
}
}

Expand Down Expand Up @@ -214,6 +235,7 @@ fn parse_tools(
install_dir: install_dir.clone(),
version_cmd_args: tool_info.version_cmd.to_vec(),
version_regex: tool_info.version_regex.to_string(),
export_vars: tool_info.export_vars.as_ref().map(|v| v.0.clone()).unwrap_or_default(),
..Default::default()
};

Expand Down Expand Up @@ -256,7 +278,7 @@ fn parse_tools(
tool.export_path = PathBuf::new().join("tools").join(&tool.name).join(&tool.version);

// export_path has two layers if indirection...
// it seams only the first array is ever used
// it seems only the first array is ever used
let first_path = tool_info.export_paths.first();

if let Some(path) = first_path {
Expand Down Expand Up @@ -342,6 +364,8 @@ pub struct EspIdf {
pub repository: git::Repository,
/// The binary paths of all tools concatenated with the system `PATH` env variable.
pub exported_path: OsString,
/// The environment variables of all tools.
pub exported_env_vars: HashMap<String, String>,
/// 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.
Expand Down Expand Up @@ -417,6 +441,10 @@ impl EspIdf {
version: EspIdfVersion::try_from(&repo),
repository: repo,
exported_path: path_var,
// Env vars are already set by the parent process
// and since it is very difficult to extract these,
// we just assume that they are already set correctly
exported_env_vars: HashMap::new(),
venv_python: python,
is_managed_espidf: true,
})
Expand Down Expand Up @@ -691,9 +719,10 @@ impl Installer {
}

// End Tools install

// Create PATH

// All tools are installed -> infer there PATH variable by using the information out of tools.json
// All tools are installed -> infer the PATH variable by using the information out of tools.json
let mut tools_path: Vec<PathBuf> = tools_vec
.iter()
.map(|tool| tool.abs_export_path())
Expand All @@ -711,11 +740,17 @@ impl Installer {
.chain(env::split_paths(&env::var_os("PATH").unwrap_or_default())),
)?;

let env_vars = tools_vec
.iter()
.flat_map(|tool| tool.abs_export_env_vars())
.collect::<HashMap<_, _>>();

log::debug!("Using PATH='{}'", &paths.to_string_lossy());

Ok(EspIdf {
repository,
exported_path: paths,
exported_env_vars: env_vars,
venv_python,
version: esp_version,
is_managed_espidf: managed_repo,
Expand Down
43 changes: 27 additions & 16 deletions src/espidf/tools_schema.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
//! This is an autogenerated file and includes small manual fixes
//!
//! generated with [cargo-typify](https://github.com/oxidecomputer/typify) v0.1
//! using command "cargo typify -B tools_schema.json"
//! and the [tools_schema.json]( https://github.com/espressif/esp-idf/blob/master/tools/tools_schema.json ) file
//!
//! manual adjustments:
//! - made everything pub into pub (crate)
//! - manual adding missing macos-arm64 prob to VersionInfo not included in schema -> https://github.com/espressif/esp-idf/issues/13853
//! - Switched out EnvVars with serde_json Value, cause the json schema is using a patternPropertie to "specify" the field key **sick**
#![allow(clippy::redundant_closure_call)]
#![allow(clippy::needless_lifetimes)]
#![allow(clippy::match_single_binding)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::to_string_trait_impl)]

/// This is an autogenerated file and includes small manual fixes
///
/// generated with [cargo-typify](https://github.com/oxidecomputer/typify) v0.1
/// using command "cargo typify -B tools_schema.json"
/// and the [tools_schema.json]( https://github.com/espressif/esp-idf/blob/master/tools/tools_schema.json ) file
///
/// manual adjustments:
/// - made everything pub into pub (crate)
/// - manual adding missing macos-arm64 prob to VersionInfo not included in schema -> https://github.com/espressif/esp-idf/issues/13853
/// - Switched out EnvVars with serde_json Value, cause the json schema is using a patternPropertie to "specify" the field key **sick**
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;

#[doc = r" Error types."]
pub(crate) mod error {
Expand Down Expand Up @@ -80,7 +81,7 @@ impl From<Vec<String>> for ArrayOfStrings {
Self(value)
}
}
/* #[doc = "Collection of environment variables. Keys and values are the environment variable names and values, respectively."]
#[doc = "Collection of environment variables. Keys and values are the environment variable names and values, respectively."]
#[doc = r""]
#[doc = r" <details><summary>JSON schema</summary>"]
#[doc = r""]
Expand All @@ -99,12 +100,22 @@ impl From<Vec<String>> for ArrayOfStrings {
#[doc = r" </details>"]
#[derive(Clone, Debug, Deserialize, Serialize)]
//#[serde(deny_unknown_fields)]
pub(crate) struct EnvVars {}
pub(crate) struct EnvVars(pub(crate) HashMap<String, String>);
impl From<EnvVars> for HashMap<String, String> {
fn from(value: EnvVars) -> Self {
value.0
}
}
impl From<&EnvVars> for EnvVars {
fn from(value: &EnvVars) -> Self {
value.clone()
}
} */
}
impl From<HashMap<String, String>> for EnvVars {
fn from(value: HashMap<String, String>) -> Self {
Self(value)
}
}
#[doc = "Array of paths to be exported (added to PATH). Each item in the array is relative to the directory where the tool will be installed."]
#[doc = r""]
#[doc = r" <details><summary>JSON schema</summary>"]
Expand Down Expand Up @@ -333,7 +344,7 @@ pub(crate) struct PlatformOverrideInfo {
pub(crate) export_paths: Option<ExportPaths>,
#[doc = "Platform-specific replacement for toolInfo/export_vars"]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) export_vars: Option<Value>,
pub(crate) export_vars: Option<EnvVars>,
#[doc = "Platform-specific replacement for toolInfo/install"]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) install: Option<InstallRequirementInfo>,
Expand Down Expand Up @@ -592,7 +603,7 @@ pub(crate) struct ToolInfo {
pub(crate) export_paths: ExportPaths,
#[doc = "Some variable expansions are done on the values. 1) ${TOOL_PATH} is replaced with the directory where the tool is installed."]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) export_vars: Option<Value>,
pub(crate) export_vars: Option<EnvVars>,
#[doc = "URL of the page with information about the tool"]
pub(crate) info_url: String,
#[doc = "If 'always', the tool will be installed by default. If 'on_request', tool will be installed when specifically requested. If 'never', tool will not be considered for installation."]
Expand Down

0 comments on commit 480cf6d

Please sign in to comment.