diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6b55c6d..797ea22 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -3,6 +3,8 @@ name: Continuous Integration on: push: + branches: + - main paths-ignore: - "**/README.md" - "**/cd.yml" @@ -16,7 +18,7 @@ env: jobs: continuous-integration: - name: Checks + name: cargo ${{ matrix.action.command }} - ${{ matrix.job.os }} runs-on: ${{ matrix.job.os }} strategy: fail-fast: false @@ -29,6 +31,19 @@ jobs: - os: windows-latest os-name: windows binary-postfix: ".exe" + action: + - command: build + args: --release + - command: test + args: --all-features --workspace + - command: fmt + args: --all -- --check + - command: clippy + args: --all-targets --all-features --workspace -- -D warnings + - command: doc + args: --no-deps --document-private-items --all-features --workspace --examples + - command: publish + args: --dry-run steps: - name: Install dependencies if: ${{ matrix.job.os == 'ubuntu-latest' }} @@ -45,34 +60,10 @@ jobs: - name: Build uses: actions-rs/cargo@v1 with: - command: build - args: --release - - name: Test suite - uses: actions-rs/cargo@v1 - with: - command: test - args: --all-features --workspace - - name: Format check - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check - - name: Clippy check - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --all-targets --all-features --workspace -- -D warnings - - name: Docs - uses: actions-rs/cargo@v1 - with: - command: doc - args: --no-deps --document-private-items --all-features --workspace --examples - - name: Publish dry run - uses: actions-rs/cargo@v1 - with: - command: publish - args: --dry-run + command: ${{ matrix.action.command }} + args: ${{ matrix.action.args }} - name: Archive artifact + if: ${{ matrix.action.command == 'build' }} uses: actions/upload-artifact@v3 with: name: espup-${{ matrix.job.os-name }}${{ matrix.job.binary-postfix }} diff --git a/Cargo.lock b/Cargo.lock index 4414b6b..888c3bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,9 +144,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.18" +version = "4.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335867764ed2de42325fafe6d18b8af74ba97ee0c590fa016f157535b42ab04b" +checksum = "91b9970d7505127a162fdaa9b96428d28a479ba78c9ec7550a63a5d9863db682" dependencies = [ "atty", "bitflags", @@ -159,9 +159,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.0.18" +version = "4.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a1b0f6422af32d5da0c58e2703320f379216ee70198241c84173a8c5ac28f3" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" dependencies = [ "heck", "proc-macro-error", @@ -382,9 +382,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" dependencies = [ "atty", "humantime", @@ -416,7 +416,7 @@ dependencies = [ [[package]] name = "espup" -version = "0.2.1" +version = "0.2.2-dev" dependencies = [ "anyhow", "clap", @@ -1119,9 +1119,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ "aho-corasick", "memchr", diff --git a/Cargo.toml b/Cargo.toml index ac7b500..6192f2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "espup" -version = "0.2.1" +version = "0.2.2-dev" authors = ["Sergio Gasquez Arcos "] edition = "2021" license = "MIT OR Apache-2.0" @@ -15,7 +15,7 @@ rust-version = "1.62" [dependencies] anyhow = "1.0.66" -clap = { version = "4.0.18", features = ["derive"] } +clap = { version = "4.0.22", features = ["derive"] } dirs = "4.0.0" flate2 = "1.0.22" guess_host_triple = "0.1.3" @@ -26,7 +26,7 @@ xz2 = "0.1.6" console = "0.15.1" tempfile = "3.3.0" log = "0.4.17" -env_logger = "0.9.0" +env_logger = "0.9.3" embuild = { version = "0.30.4", features = ["espidf", "git"] } strum = { version = "0.24", features = ["derive"] } strum_macros = "0.24.3" @@ -34,7 +34,7 @@ toml = "0.5.9" directories-next = "2.0.0" serde = { version = "1.0.146", features = ["derive"] } miette = "5.4.1" -regex = "1.6.0" +regex = "1.7.0" serde_json = "1.0.87" thiserror = "1.0.37" diff --git a/README.md b/README.md index 2cd1d15..2b7ad50 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ See [Usage](#usage) section for more details. - [`std`](https://esp-rs.github.io/book/overview/using-the-standard-library.html): Installing `esp-idf` via `espup` is not mandatory, as [`esp-idf-sys`](https://github.com/esp-rs/esp-idf-sys) already takes care of it, but has some benefits. ```sh - espup install --espidf-version + espup install --esp-idf-version # Unix . ./export-esp.sh # Windows @@ -150,7 +150,7 @@ Options: -d, --default-host Target triple of the host - -e, --espidf-version + -e, --esp-idf-version ESP-IDF version to install. If empty, no esp-idf is installed. Version format: - `commit:`: Uses the commit `` of the `esp-idf` repository. @@ -168,12 +168,10 @@ Options: -f, --export-file Destination of the generated export file - [default: export-esp.sh] - -c, --extra-crates Comma or space list of extra crates to install - [default: cargo-espflash] + [default: ] -x, --llvm-version LLVM version diff --git a/src/config.rs b/src/config.rs index 4bed5c3..08bb98e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,11 +12,11 @@ use std::{ #[derive(Debug, Deserialize, Serialize, Default, Clone)] pub struct Config { /// ESP-IDF version - pub espidf_version: Option, + pub esp_idf_version: Option, /// Destination of the generated export file. pub export_file: PathBuf, /// Extra crates to installed. - pub extra_crates: HashSet, + pub extra_crates: Option>, /// Host triple pub host_triple: HostTriple, /// LLVM toolchain path. diff --git a/src/host_triple.rs b/src/host_triple.rs index 6659cd1..8f83084 100644 --- a/src/host_triple.rs +++ b/src/host_triple.rs @@ -38,3 +38,70 @@ pub fn get_host_triple(host_triple_arg: Option) -> Result), /// Uninstalls esp-rs environment Uninstall(UninstallOpts), /// Updates Xtensa Rust toolchain @@ -77,13 +80,13 @@ pub struct InstallOpts { /// /// When using this option, `ldproxy` crate will also be installed. #[arg(short = 'e', long, required = false)] - pub espidf_version: Option, + pub esp_idf_version: Option, /// Destination of the generated export file. - #[arg(short = 'f', long, default_value = DEFAULT_EXPORT_FILE)] - pub export_file: PathBuf, + #[arg(short = 'f', long)] + pub export_file: Option, /// Comma or space list of extra crates to install. - #[arg(short = 'c', long, default_value = "cargo-espflash")] - pub extra_crates: String, + #[arg(short = 'c', long, required = false, value_parser = Crate::parse_crates)] + pub extra_crates: Option>, /// LLVM version. #[arg(short = 'x', long, default_value = "15", value_parser = ["15"])] pub llvm_version: String, @@ -97,8 +100,8 @@ pub struct InstallOpts { #[arg(short = 'm', long)] pub profile_minimal: bool, /// Comma or space separated list of targets [esp32,esp32s2,esp32s3,esp32c2,esp32c3,all]. - #[arg(short = 't', long, default_value = "all")] - pub targets: String, + #[arg(short = 't', long, default_value = "all", value_parser = parse_targets)] + pub targets: HashSet, /// Xtensa Rust toolchain version. #[arg(short = 'v', long, value_parser = XtensaRust::parse_version)] pub toolchain_version: Option, @@ -129,11 +132,10 @@ fn install(args: InstallOpts) -> Result<()> { initialize_logger(&args.log_level); info!("{} Installing esp-rs", emoji::DISC); - let targets: HashSet = parse_targets(&args.targets).unwrap(); + let targets = args.targets; let host_triple = get_host_triple(args.default_host)?; - let mut extra_crates: HashSet = args.extra_crates.split(',').map(Crate::new).collect(); + let mut extra_crates = args.extra_crates; let mut exports: Vec = Vec::new(); - let export_file = args.export_file.clone(); let xtensa_rust = if targets.contains(&Target::ESP32) || targets.contains(&Target::ESP32S2) || targets.contains(&Target::ESP32S3) @@ -148,6 +150,7 @@ fn install(args: InstallOpts) -> Result<()> { } else { None }; + let export_file = get_export_file(args.export_file)?; let llvm = Llvm::new(args.llvm_version, args.profile_minimal, &host_triple); debug!( @@ -165,8 +168,8 @@ fn install(args: InstallOpts) -> Result<()> { emoji::INFO, host_triple, targets, - &args.espidf_version, - export_file, + &args.esp_idf_version, + &export_file, &extra_crates, llvm, &args.nightly_version, @@ -176,7 +179,7 @@ fn install(args: InstallOpts) -> Result<()> { ); #[cfg(windows)] - check_arguments(&targets, &args.espidf_version)?; + check_arguments(&targets, &args.esp_idf_version)?; check_rust_installation(&args.nightly_version)?; @@ -190,21 +193,22 @@ fn install(args: InstallOpts) -> Result<()> { install_riscv_target(&args.nightly_version)?; } - if let Some(espidf_version) = &args.espidf_version { - let repo = EspIdfRepo::new(espidf_version, args.profile_minimal, &targets); + if let Some(esp_idf_version) = &args.esp_idf_version { + let repo = EspIdfRepo::new(esp_idf_version, args.profile_minimal, &targets); exports.extend(repo.install()?); - extra_crates.insert(Crate::new("ldproxy")); + if let Some(ref mut extra_crates) = extra_crates { + extra_crates.insert(Crate::new("ldproxy")); + } else { + let mut crates = HashSet::new(); + crates.insert(Crate::new("ldproxy")); + extra_crates = Some(crates); + }; } else { exports.extend(install_gcc_targets(&targets, &host_triple)?); } - debug!( - "{} Installing the following crates: {:#?}", - emoji::DEBUG, - extra_crates - ); - for extra_crate in &extra_crates { - extra_crate.install()?; + if let Some(ref extra_crates) = &extra_crates { + install_extra_crates(extra_crates)?; } if args.profile_minimal { @@ -215,12 +219,14 @@ fn install(args: InstallOpts) -> Result<()> { info!("{} Saving configuration file", emoji::WRENCH); let config = Config { - espidf_version: args.espidf_version, + esp_idf_version: args.esp_idf_version, export_file, - extra_crates: extra_crates - .iter() - .map(|x| x.name.clone()) - .collect::>(), + extra_crates: extra_crates.as_ref().map(|extra_crates| { + extra_crates + .iter() + .map(|x| x.name.clone()) + .collect::>() + }), host_triple, llvm_path: llvm.path, nightly_version: args.nightly_version, @@ -257,10 +263,10 @@ fn uninstall(args: UninstallOpts) -> Result<()> { info!("{} Deleting Xtensa LLVM", emoji::WRENCH); remove_dir_all(config.llvm_path)?; - if let Some(espidf_version) = config.espidf_version { - info!("{} Deleting ESP-IDF {}", emoji::WRENCH, espidf_version); + if let Some(esp_idf_version) = config.esp_idf_version { + info!("{} Deleting ESP-IDF {}", emoji::WRENCH, esp_idf_version); let repo = EspIdfRemote { - git_ref: parse_esp_idf_git_ref(&espidf_version), + git_ref: parse_esp_idf_git_ref(&esp_idf_version), repo_url: Some(DEFAULT_GIT_REPOSITORY.to_string()), }; remove_dir_all(get_install_path(repo).parent().unwrap())?; @@ -273,8 +279,10 @@ fn uninstall(args: UninstallOpts) -> Result<()> { } info!("{} Uninstalling extra crates", emoji::WRENCH); - for extra_crate in &config.extra_crates { - cmd!("cargo", "uninstall", extra_crate).run()?; + if let Some(extra_crates) = &config.extra_crates { + for extra_crate in extra_crates { + cmd!("cargo", "uninstall", extra_crate).run()?; + } } clear_dist_folder()?; @@ -337,7 +345,7 @@ fn update(args: UpdateOpts) -> Result<()> { fn main() -> Result<()> { match Cli::parse().subcommand { - SubCommand::Install(args) => install(args), + SubCommand::Install(args) => install(*args), SubCommand::Update(args) => update(args), SubCommand::Uninstall(args) => uninstall(args), } @@ -353,8 +361,23 @@ fn clear_dist_folder() -> Result<()> { Ok(()) } +/// Returns the absolute path to the export file, uses the DEFAULT_EXPORT_FILE if no arg is provided. +fn get_export_file(export_file: Option) -> Result { + if let Some(export_file) = export_file { + if export_file.is_absolute() { + Ok(export_file) + } else { + let current_dir = std::env::current_dir()?; + Ok(current_dir.join(export_file)) + } + } else { + let home_dir = home_dir().unwrap(); + Ok(home_dir.join(DEFAULT_EXPORT_FILE)) + } +} + /// Creates the export file with the necessary environment variables. -pub fn export_environment(export_file: &PathBuf, exports: &[String]) -> Result<()> { +fn export_environment(export_file: &PathBuf, exports: &[String]) -> Result<()> { info!("{} Creating export file", emoji::WRENCH); let mut file = File::create(export_file)?; for e in exports.iter() { @@ -369,7 +392,7 @@ pub fn export_environment(export_file: &PathBuf, exports: &[String]) -> Result<( ); #[cfg(unix)] warn!( - "{} PLEASE set up the environment variables running: '. ./{}'", + "{} PLEASE set up the environment variables running: '. {}'", emoji::INFO, export_file.display() ); @@ -382,11 +405,13 @@ pub fn export_environment(export_file: &PathBuf, exports: &[String]) -> Result<( #[cfg(windows)] /// For Windows, we need to check that we are installing all the targets if we are installing esp-idf. + pub fn check_arguments( targets: &HashSet, espidf_version: &Option, ) -> Result<(), Error> { if espidf_version.is_some() + && (!targets.contains(&Target::ESP32) || !targets.contains(&Target::ESP32C3) || !targets.contains(&Target::ESP32S2) @@ -397,3 +422,32 @@ pub fn check_arguments( Ok(()) } + +#[cfg(test)] +mod tests { + use crate::{get_export_file, DEFAULT_EXPORT_FILE}; + use dirs::home_dir; + use std::{env::current_dir, path::PathBuf}; + + #[test] + #[allow(unused_variables)] + fn test_get_export_file() { + // No arg provided + let home_dir = home_dir().unwrap(); + let export_file = home_dir.join(DEFAULT_EXPORT_FILE); + assert!(matches!(get_export_file(None), Ok(export_file))); + // Relative path + let current_dir = current_dir().unwrap(); + let export_file = current_dir.join("export.sh"); + assert!(matches!( + get_export_file(Some(PathBuf::from("export.sh"))), + Ok(export_file) + )); + // Absolute path + let export_file = PathBuf::from("/home/user/export.sh"); + assert!(matches!( + get_export_file(Some(PathBuf::from("/home/user/export.sh"))), + Ok(export_file) + )); + } +} diff --git a/src/toolchain/rust.rs b/src/toolchain/rust.rs index 27b6889..c33ad5c 100644 --- a/src/toolchain/rust.rs +++ b/src/toolchain/rust.rs @@ -12,7 +12,7 @@ use log::{debug, info, warn}; use regex::Regex; use reqwest::header; use serde::{Deserialize, Serialize}; -use std::fmt::Debug; +use std::{collections::HashSet, fmt::Debug}; use std::{env, fs::remove_dir_all, path::PathBuf, process::Stdio}; /// Xtensa Rust Toolchain repository @@ -222,8 +222,24 @@ impl Crate { name: name.to_string(), } } + + /// Parses the extra crates to be installed. + pub fn parse_crates(arg: &str) -> Result> { + Ok(arg.split(',').map(Crate::new).collect()) + } } +pub fn install_extra_crates(crates: &HashSet) -> Result<()> { + debug!( + "{} Installing the following crates: {:#?}", + emoji::DEBUG, + crates + ); + for c in crates { + c.install()?; + } + Ok(()) +} /// Gets the artifact extension based on the host architecture. fn get_artifact_extension(host_triple: &HostTriple) -> &str { match host_triple { @@ -379,7 +395,8 @@ fn install_rust_nightly(version: &str) -> Result<(), Error> { #[cfg(test)] mod tests { - use crate::toolchain::rust::XtensaRust; + use crate::toolchain::rust::{Crate, XtensaRust}; + use std::collections::HashSet; #[test] fn test_xtensa_rust_parse_version() { @@ -391,4 +408,33 @@ mod tests { assert!(XtensaRust::parse_version("1..1.1").is_err()); assert!(XtensaRust::parse_version("1._.*.1").is_err()); } + + #[test] + #[allow(unused_variables)] + fn test_parse_crates() { + let mut crates: HashSet = HashSet::new(); + crates.insert(Crate::new("ldproxy")); + assert!(matches!(Crate::parse_crates("ldproxy"), Ok(crates))); + let mut crates: HashSet = HashSet::new(); + crates.insert(Crate::new("ldproxy")); + crates.insert(Crate::new("espflash")); + assert!(matches!( + Crate::parse_crates("ldproxy, espflash"), + Ok(crates) + )); + let mut crates: HashSet = HashSet::new(); + crates.insert(Crate::new("cargo-generate")); + crates.insert(Crate::new("sccache")); + assert!(matches!( + Crate::parse_crates("cargo-generate sccache"), + Ok(crates) + )); + let mut crates: HashSet = HashSet::new(); + crates.insert(Crate::new("cargo-binstall")); + crates.insert(Crate::new("espmonitor")); + assert!(matches!( + Crate::parse_crates("cargo-binstall,espmonitor"), + Ok(crates) + )); + } }