From cc83f41c4a140a6cce56d6903f4b7a6625e91cae Mon Sep 17 00:00:00 2001 From: Alex Bean Date: Fri, 10 May 2024 20:40:32 +0200 Subject: [PATCH] feat: pop install (#149) * scaffold pop install * include scripts * implement script execution * update script * links * fix formatting * feat: installation process in rust code * feat: flag --skip-confirmation * refactor: avoid repetition * test: pop install CI * fix: merge main * docs: remove Substrate word * fix: remove sudo from commands and merge install.yml * chore: merge and refactor .yml * chore: add debian too * chore: pop install CI only on main * fix: in CI run install pop and run pop install separately * refactor: dependencies in enum * refactor: dependencies in enum * refactor: sort enum * chore: feedback * chore: test in ci for arch and redhat and some fixes * chore: change in ci * fix: ci arch distribution * chore: change ci to only triggers in main * fix: command help message * refactor: remove unnecesary prefix and merge commands * refactor: remove unnecesary code * chore: fix formatting --------- Co-authored-by: Abhishek Shah --- .github/workflows/install.yml | 72 ++++- Cargo.lock | 12 + Cargo.toml | 1 + README.md | 2 +- crates/pop-cli/Cargo.toml | 2 + crates/pop-cli/src/commands/install/mod.rs | 305 +++++++++++++++++++++ crates/pop-cli/src/commands/mod.rs | 1 + crates/pop-cli/src/main.rs | 29 +- crates/pop-parachains/src/up.rs | 2 +- 9 files changed, 411 insertions(+), 15 deletions(-) create mode 100644 crates/pop-cli/src/commands/install/mod.rs diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index f2f40332..d747e2bd 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -11,6 +11,73 @@ defaults: shell: bash jobs: + arch: + runs-on: ubuntu-latest + container: archlinux:latest + steps: + - uses: actions/checkout@v4 + - name: Install prerequisites + run: pacman -Syu --needed --noconfirm cmake curl git base-devel + - name: Install Rust + run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + - name: Install Pop + run: | + . "$HOME/.cargo/env" + cargo install --locked --path ./crates/pop-cli + - name: Run Pop install + run: | + . "$HOME/.cargo/env" + pop install -y + debian: + runs-on: ubuntu-latest + container: debian + steps: + - uses: actions/checkout@v4 + - name: Install prerequisites + run: apt-get update && apt-get -y install build-essential cmake curl git + - name: Install Rust + run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + - name: Install Pop + run: | + . "$HOME/.cargo/env" + cargo install --locked --path ./crates/pop-cli + - name: Run Pop Install + run: | + . "$HOME/.cargo/env" + pop install -y + macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Install prerequisites + run: brew update && brew install cmake openssl protobuf + - name: Install Rust + run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + - name: Install Pop + run: | + . "$HOME/.cargo/env" + cargo install --locked --path ./crates/pop-cli + - name: Run Pop Install + run: | + . "$HOME/.cargo/env" + pop install -y + redhat: + runs-on: ubuntu-latest + container: redhat/ubi8 + steps: + - uses: actions/checkout@v4 + - name: Install prerequisites + run: yum update -y && yum install -y perl-IPC-Cmd clang curl git make cmake protobuf-compiler gcc pkg-config openssl-devel + - name: Install Rust + run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + - name: Install Pop + run: | + . "$HOME/.cargo/env" + cargo install --locked --path ./crates/pop-cli + - name: Run Pop install + run: | + . "$HOME/.cargo/env" + pop install -y ubuntu: runs-on: ubuntu-latest container: ubuntu @@ -24,4 +91,7 @@ jobs: run: | . "$HOME/.cargo/env" cargo install --locked --path ./crates/pop-cli - pop --version \ No newline at end of file + - name: Run Pop install + run: | + . "$HOME/.cargo/env" + pop install -y \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 459c23ac..304c85a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4764,6 +4764,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "os_info" +version = "3.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" +dependencies = [ + "log", + "windows-sys 0.52.0", +] + [[package]] name = "os_pipe" version = "1.1.5" @@ -5311,9 +5321,11 @@ dependencies = [ "dirs", "duct", "git2", + "os_info", "pop-contracts", "pop-parachains", "predicates", + "reqwest", "sp-core 30.0.0", "sp-weights 29.0.0", "strum 0.26.2", diff --git a/Cargo.toml b/Cargo.toml index 04595670..5ff96284 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,3 +50,4 @@ cliclack = "0.2" console = "0.15" strum = "0.26" strum_macros = "0.26" +os_info = { version = "3", default-features = false } diff --git a/README.md b/README.md index 4a9fa082..b64a5158 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ You can install Pop CLI as follows: ```shell cargo install --locked --git https://github.com/r0gue-io/pop-cli ``` - +> For detailed instructions on how to install Pop CLI, please refer to our documentation: https://learn.onpop.io/pop/v/pop-cli/welcome/installing-pop-cli > :information_source: A [crates.io](https://crates.io/crates/pop-cli) version will be available soon! ## Getting Started diff --git a/crates/pop-cli/Cargo.toml b/crates/pop-cli/Cargo.toml index 3ea23f1b..362db78d 100644 --- a/crates/pop-cli/Cargo.toml +++ b/crates/pop-cli/Cargo.toml @@ -14,6 +14,8 @@ duct.workspace = true tempfile.workspace = true url.workspace = true tokio.workspace = true +reqwest.workspace = true +os_info.workspace = true # pop-cli clap.workspace = true diff --git a/crates/pop-cli/src/commands/install/mod.rs b/crates/pop-cli/src/commands/install/mod.rs new file mode 100644 index 00000000..66af7b08 --- /dev/null +++ b/crates/pop-cli/src/commands/install/mod.rs @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-3.0 +use crate::style::{style, Theme}; +use anyhow::Context; +use clap::Args; +use cliclack::{clear_screen, confirm, intro, log, outro, set_theme}; +use duct::cmd; +use os_info::Type; +use strum::Display; +use tokio::{fs, process::Command}; +use Dependencies::*; + +#[derive(Display)] +pub enum Dependencies { + #[strum(serialize = "build-essential")] + BuildEssential, + #[strum(serialize = "clang")] + Clang, + #[strum(serialize = "clang-devel")] + ClangDevel, + #[strum(serialize = "cmake")] + Cmake, + #[strum(serialize = "curl")] + Curl, + #[strum(serialize = "gcc")] + Gcc, + #[strum(serialize = "git")] + Git, + #[strum(serialize = "homebrew")] + Homebrew, + #[strum(serialize = "libclang-dev")] + LibClang, + #[strum(serialize = "libssl-dev")] + Libssl, + #[strum(serialize = "make")] + Make, + #[strum(serialize = "openssl")] + Openssl, + #[strum(serialize = "openssl-devel")] + OpenSslDevel, + #[strum(serialize = "pkg-config")] + PkgConfig, + #[strum(serialize = "protobuf")] + Protobuf, + #[strum(serialize = "protobuf-compiler")] + ProtobufCompiler, + #[strum(serialize = "rustup")] + Rustup, +} +#[derive(Args)] +#[command(args_conflicts_with_subcommands = true)] +/// Setup user environment for development +pub(crate) struct InstallArgs { + /// Automatically install all dependencies required without prompting for confirmation. + #[clap(short('y'), long)] + skip_confirm: bool, +} + +impl InstallArgs { + pub(crate) async fn execute(self) -> anyhow::Result<()> { + clear_screen()?; + set_theme(Theme); + intro(format!( + "{}: Install dependencies for development", + style(" Pop CLI ").black().on_magenta() + ))?; + if cfg!(target_os = "macos") { + log::info("ℹ️ Mac OS (Darwin) detected.")?; + install_mac(self.skip_confirm).await?; + } else if cfg!(target_os = "linux") { + match os_info::get().os_type() { + Type::Arch => { + log::info("ℹ️ Arch Linux detected.")?; + install_arch(self.skip_confirm).await?; + }, + Type::Debian => { + log::info("ℹ️ Debian Linux detected.")?; + install_debian(self.skip_confirm).await?; + }, + Type::Redhat => { + log::info("ℹ️ Redhat Linux detected.")?; + install_redhat(self.skip_confirm).await?; + }, + Type::Ubuntu => { + log::info("ℹ️ Ubuntu detected.")?; + install_ubuntu(self.skip_confirm).await?; + }, + _ => return not_supported_message(), + } + } else { + return not_supported_message(); + } + install_rustup().await?; + outro("✅ Installation complete.")?; + Ok(()) + } +} + +async fn install_mac(skip_confirm: bool) -> anyhow::Result<()> { + log::info("More information about the packages to be installed here: https://docs.substrate.io/install/macos/")?; + if !skip_confirm { + prompt_for_confirmation(&format!( + "{}, {}, {}, {} and {}", + Dependencies::Homebrew, + Dependencies::Protobuf, + Dependencies::Openssl, + Dependencies::Rustup, + Dependencies::Cmake, + ))? + } + install_homebrew().await?; + cmd("brew", vec!["update"]).run()?; + cmd("brew", vec!["install", &Protobuf.to_string(), &Openssl.to_string(), &Cmake.to_string()]) + .run()?; + + Ok(()) +} + +async fn install_arch(skip_confirm: bool) -> anyhow::Result<()> { + log::info("More information about the packages to be installed here: https://docs.substrate.io/install/linux/")?; + if !skip_confirm { + prompt_for_confirmation(&format!( + "{}, {}, {}, {}, {} and {}", + Curl, Git, Clang, Make, Openssl, Rustup, + ))? + } + cmd( + "pacman", + vec![ + "-Syu", + "--needed", + "--noconfirm", + &Curl.to_string(), + &Git.to_string(), + &Clang.to_string(), + &Make.to_string(), + &Openssl.to_string(), + ], + ) + .run()?; + + Ok(()) +} +async fn install_ubuntu(skip_confirm: bool) -> anyhow::Result<()> { + log::info("More information about the packages to be installed here: https://docs.substrate.io/install/linux/")?; + if !skip_confirm { + prompt_for_confirmation(&format!( + "{}, {}, {}, {}, {} and {}", + Git, Clang, Curl, Libssl, ProtobufCompiler, Rustup, + ))? + } + cmd( + "apt", + vec![ + "install", + "--assume-yes", + &Git.to_string(), + &Clang.to_string(), + &Curl.to_string(), + &Libssl.to_string(), + &ProtobufCompiler.to_string(), + ], + ) + .run()?; + + Ok(()) +} + +async fn install_debian(skip_confirm: bool) -> anyhow::Result<()> { + log::info("More information about the packages to be installed here: https://docs.substrate.io/install/linux/")?; + if !skip_confirm { + prompt_for_confirmation(&format!( + "{}, {}, {}, {}, {}, {}, {}, {}, {} and {}", + Cmake, + PkgConfig, + Libssl, + Git, + Gcc, + BuildEssential, + ProtobufCompiler, + Clang, + LibClang, + Rustup, + ))? + } + cmd( + "apt", + vec![ + "install", + "-y", + &Cmake.to_string(), + &PkgConfig.to_string(), + &Libssl.to_string(), + &Git.to_string(), + &Gcc.to_string(), + &BuildEssential.to_string(), + &ProtobufCompiler.to_string(), + &Clang.to_string(), + &LibClang.to_string(), + ], + ) + .run()?; + + Ok(()) +} + +async fn install_redhat(skip_confirm: bool) -> anyhow::Result<()> { + log::info("More information about the packages to be installed here: https://docs.substrate.io/install/linux/")?; + if !skip_confirm { + prompt_for_confirmation(&format!( + "{}, {}, {}, {}, {}, {}, {} and {}", + Cmake, OpenSslDevel, Git, Protobuf, ProtobufCompiler, Clang, ClangDevel, Rustup, + ))? + } + cmd("yum", vec!["update", "-y"]).run()?; + cmd("yum", vec!["groupinstall", "-y", "'Development Tool"]).run()?; + cmd( + "yum", + vec![ + "install", + "-y", + &Cmake.to_string(), + &OpenSslDevel.to_string(), + &Git.to_string(), + &Protobuf.to_string(), + &ProtobufCompiler.to_string(), + &Clang.to_string(), + &ClangDevel.to_string(), + ], + ) + .run()?; + + Ok(()) +} + +fn prompt_for_confirmation(message: &str) -> anyhow::Result<()> { + if !confirm(format!( + "📦 Do you want to proceed with the installation of the following packages: {} ?", + message + )) + .initial_value(true) + .interact()? + { + return Err(anyhow::anyhow!("🚫 You have cancelled the installation process.")); + } + Ok(()) +} + +fn not_supported_message() -> anyhow::Result<()> { + log::error("This OS is not supported at present")?; + log::warning("⚠️ Please refer to https://docs.substrate.io/install/ for setup information.")?; + Ok(()) +} + +async fn install_rustup() -> anyhow::Result<()> { + match cmd("which", vec!["rustup"]).read() { + Ok(output) => { + log::info(format!("ℹ️ rustup installed already at {}.", output))?; + cmd("rustup", vec!["update"]).run()?; + cmd("rustup", vec!["default", "stable"]).run()?; + }, + Err(_) => { + let spinner = cliclack::spinner(); + spinner.start("Installing rustup ..."); + run_external_script("https://sh.rustup.rs").await?; + outro("rustup installed!")?; + cmd("source", vec!["~/.cargo/env"]).run()?; + cmd("rustup", vec!["default", "stable"]).run()?; + }, + } + cmd("rustup", vec!["update", "nightly"]).run()?; + cmd("rustup", vec!["target", "add", "wasm32-unknown-unknown", "--toolchain", "nightly"]) + .run()?; + + Ok(()) +} + +async fn install_homebrew() -> anyhow::Result<()> { + match cmd("which", vec!["brew"]).read() { + Ok(output) => log::info(format!("ℹ️ Homebrew installed already at {}.", output))?, + Err(_) => { + run_external_script( + "https://raw.githubusercontent.com/Homebrew/install/master/install.sh", + ) + .await? + }, + } + Ok(()) +} + +async fn run_external_script(script_url: &str) -> anyhow::Result<()> { + let temp = tempfile::tempdir()?; + let scripts_path = temp.path().join("install.sh"); + let client = reqwest::Client::new(); + let script = client + .get(script_url) + .send() + .await + .context("Network Error: Failed to fetch script from Github")? + .text() + .await?; + fs::write(scripts_path.as_path(), script).await?; + Command::new("bash").arg(scripts_path).status().await?; + temp.close()?; + Ok(()) +} diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index 9dde6917..bd9e2a00 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -2,6 +2,7 @@ pub(crate) mod build; pub(crate) mod call; +pub(crate) mod install; pub(crate) mod new; pub(crate) mod test; pub(crate) mod up; diff --git a/crates/pop-cli/src/main.rs b/crates/pop-cli/src/main.rs index 2afcd509..032d004b 100644 --- a/crates/pop-cli/src/main.rs +++ b/crates/pop-cli/src/main.rs @@ -44,6 +44,9 @@ enum Commands { #[clap(alias = "t")] #[cfg(feature = "contract")] Test(commands::test::TestArgs), + /// Set up the environment for development by installing required packages + #[clap(alias = "i")] + Install(commands::install::InstallArgs), } #[tokio::main] @@ -51,14 +54,14 @@ async fn main() -> Result<()> { let cli = Cli::parse(); match cli.command { #[cfg(any(feature = "parachain", feature = "contract"))] - Commands::New(args) => Ok(match &args.command { + Commands::New(args) => match &args.command { #[cfg(feature = "parachain")] - commands::new::NewCommands::Parachain(cmd) => cmd.execute().await?, + commands::new::NewCommands::Parachain(cmd) => cmd.execute().await, #[cfg(feature = "parachain")] - commands::new::NewCommands::Pallet(cmd) => cmd.execute().await?, + commands::new::NewCommands::Pallet(cmd) => cmd.execute().await, #[cfg(feature = "contract")] - commands::new::NewCommands::Contract(cmd) => cmd.execute().await?, - }), + commands::new::NewCommands::Contract(cmd) => cmd.execute().await, + }, #[cfg(any(feature = "parachain", feature = "contract"))] Commands::Build(args) => match &args.command { #[cfg(feature = "parachain")] @@ -67,20 +70,22 @@ async fn main() -> Result<()> { commands::build::BuildCommands::Contract(cmd) => cmd.execute(), }, #[cfg(feature = "contract")] - Commands::Call(args) => Ok(match &args.command { - commands::call::CallCommands::Contract(cmd) => cmd.execute().await?, - }), + Commands::Call(args) => match &args.command { + commands::call::CallCommands::Contract(cmd) => cmd.execute().await, + }, #[cfg(any(feature = "parachain", feature = "contract"))] - Commands::Up(args) => Ok(match &args.command { + Commands::Up(args) => match &args.command { #[cfg(feature = "parachain")] - commands::up::UpCommands::Parachain(cmd) => cmd.execute().await?, + commands::up::UpCommands::Parachain(cmd) => cmd.execute().await, #[cfg(feature = "contract")] - commands::up::UpCommands::Contract(cmd) => cmd.execute().await?, - }), + commands::up::UpCommands::Contract(cmd) => cmd.execute().await, + }, #[cfg(feature = "contract")] Commands::Test(args) => match &args.command { commands::test::TestCommands::Contract(cmd) => cmd.execute(), }, + #[cfg(any(feature = "parachain", feature = "contract"))] + Commands::Install(args) => args.execute().await, } } diff --git a/crates/pop-parachains/src/up.rs b/crates/pop-parachains/src/up.rs index 8f29b02a..94848e1e 100644 --- a/crates/pop-parachains/src/up.rs +++ b/crates/pop-parachains/src/up.rs @@ -18,7 +18,7 @@ use zombienet_sdk::{Network, NetworkConfig, NetworkConfigExt}; use zombienet_support::fs::local::LocalFileSystem; const POLKADOT_SDK: &str = "https://github.com/paritytech/polkadot-sdk"; -const POLKADOT_DEFAULT_VERSION: &str = "v1.10.0"; +const POLKADOT_DEFAULT_VERSION: &str = "v1.11.0"; pub struct Zombienet { /// The cache location, used for caching binaries.