diff --git a/Cargo.lock b/Cargo.lock index 52f0e1dd9..d6c3cdf9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3037,6 +3037,7 @@ version = "0.1.0" dependencies = [ "ansi_term 0.12.1", "anyhow", + "assert_fs", "camino", "cargo_metadata", "lazy_static", diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index ac2ed8a5f..297d907ca 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [dependencies] ansi_term = "0.12" +assert_fs = "1" anyhow = "1" camino = "1.0" cargo_metadata = "0.13" diff --git a/xtask/src/commands/cargo/mod.rs b/xtask/src/commands/cargo/mod.rs deleted file mode 100644 index 988ae6f4b..000000000 --- a/xtask/src/commands/cargo/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod runner; -mod target; - -pub(crate) use runner::CargoRunner; -pub(crate) use target::{Target, POSSIBLE_TARGETS}; diff --git a/xtask/src/commands/dist/mod.rs b/xtask/src/commands/dist.rs similarity index 79% rename from xtask/src/commands/dist/mod.rs rename to xtask/src/commands/dist.rs index 2c231edd1..85ca56726 100644 --- a/xtask/src/commands/dist/mod.rs +++ b/xtask/src/commands/dist.rs @@ -1,11 +1,8 @@ -mod strip; - -use crate::commands::{CargoRunner, Target, POSSIBLE_TARGETS}; - use anyhow::{Context, Result}; use structopt::StructOpt; -use crate::commands::dist::strip::StripRunner; +use crate::target::{Target, POSSIBLE_TARGETS}; +use crate::tools::{CargoRunner, StripRunner}; #[derive(Debug, StructOpt)] pub struct Dist { @@ -17,11 +14,11 @@ impl Dist { pub fn run(&self, verbose: bool) -> Result<()> { let cargo_runner = CargoRunner::new(verbose)?; let binary_path = cargo_runner - .build(self.target.to_owned()) + .build(&self.target, true) .with_context(|| "Could not build Rover.")?; if !cfg!(windows) { - let strip_runner = StripRunner::new(binary_path, verbose); + let strip_runner = StripRunner::new(binary_path, verbose)?; strip_runner .run() .with_context(|| "Could not strip symbols from Rover's binary")?; diff --git a/xtask/src/commands/lint.rs b/xtask/src/commands/lint.rs index 5b0d58f1b..a82506b29 100644 --- a/xtask/src/commands/lint.rs +++ b/xtask/src/commands/lint.rs @@ -1,7 +1,7 @@ use anyhow::Result; use structopt::StructOpt; -use crate::commands::CargoRunner; +use crate::tools::CargoRunner; #[derive(Debug, StructOpt)] pub struct Lint {} diff --git a/xtask/src/commands/mod.rs b/xtask/src/commands/mod.rs index 7ea8bb056..a60619563 100644 --- a/xtask/src/commands/mod.rs +++ b/xtask/src/commands/mod.rs @@ -1,10 +1,8 @@ -mod cargo; pub(crate) mod dist; pub(crate) mod lint; pub(crate) mod prep; pub(crate) mod test; -pub(crate) use cargo::{CargoRunner, Target, POSSIBLE_TARGETS}; pub(crate) use dist::Dist; pub(crate) use lint::Lint; pub(crate) use prep::Prep; diff --git a/xtask/src/commands/prep/mod.rs b/xtask/src/commands/prep/mod.rs index b355b7256..4be5ea4a0 100644 --- a/xtask/src/commands/prep/mod.rs +++ b/xtask/src/commands/prep/mod.rs @@ -1,18 +1,19 @@ mod docs; mod installers; -mod npm; use anyhow::{Context, Result}; use structopt::StructOpt; use crate::commands::prep::docs::DocsRunner; +use crate::tools::NpmRunner; #[derive(Debug, StructOpt)] pub struct Prep {} impl Prep { pub fn run(&self, verbose: bool) -> Result<()> { - npm::prepare_package(verbose)?; + let npm_runner = NpmRunner::new(verbose)?; + npm_runner.prepare_package()?; installers::update_versions()?; let docs_runner = DocsRunner::new()?; docs_runner diff --git a/xtask/src/commands/test.rs b/xtask/src/commands/test.rs index d9742193f..33affc7ac 100644 --- a/xtask/src/commands/test.rs +++ b/xtask/src/commands/test.rs @@ -1,8 +1,8 @@ use anyhow::Result; use structopt::StructOpt; -use crate::commands::CargoRunner; -use crate::commands::{Target, POSSIBLE_TARGETS}; +use crate::target::{Target, POSSIBLE_TARGETS}; +use crate::tools::{CargoRunner, GitRunner, MakeRunner}; #[derive(Debug, StructOpt)] pub struct Test { @@ -12,8 +12,21 @@ pub struct Test { impl Test { pub fn run(&self, verbose: bool) -> Result<()> { + let release = false; let cargo_runner = CargoRunner::new(verbose)?; + let git_runner = GitRunner::new(verbose)?; + cargo_runner.test(self.target.to_owned())?; + + if let Target::GnuLinux = self.target { + let make_runner = + MakeRunner::new(verbose, cargo_runner.get_bin_path(&self.target, release))?; + cargo_runner.build(&self.target, release)?; + + let repo_path = git_runner.clone_supergraph_demo()?; + make_runner.test_supergraph_demo(&repo_path)?; + } + Ok(()) } } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 9a506ce5d..4a44e2384 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,4 +1,7 @@ mod commands; + +pub(crate) mod target; +pub(crate) mod tools; pub(crate) mod utils; use ansi_term::Colour::Green; diff --git a/xtask/src/commands/cargo/target.rs b/xtask/src/target.rs similarity index 100% rename from xtask/src/commands/cargo/target.rs rename to xtask/src/target.rs diff --git a/xtask/src/commands/cargo/runner.rs b/xtask/src/tools/cargo.rs similarity index 71% rename from xtask/src/commands/cargo/runner.rs rename to xtask/src/tools/cargo.rs index 897053249..625d6e521 100644 --- a/xtask/src/commands/cargo/runner.rs +++ b/xtask/src/tools/cargo.rs @@ -3,27 +3,32 @@ use std::{collections::HashMap, str::FromStr}; use anyhow::{anyhow, Result}; use camino::Utf8PathBuf; -use crate::commands::Target; +use crate::target::Target; +use crate::tools::Runner; use crate::utils::{self, CommandOutput}; pub(crate) struct CargoRunner { - rover_package_directory: Utf8PathBuf, - verbose: bool, + cargo_package_directory: Utf8PathBuf, + runner: Runner, } impl CargoRunner { pub(crate) fn new(verbose: bool) -> Result { - let rover_package_directory = utils::project_root()?; + let runner = Runner::new("cargo", verbose)?; + let cargo_package_directory = utils::project_root()?; Ok(CargoRunner { - rover_package_directory, - verbose, + cargo_package_directory, + runner, }) } - pub(crate) fn build(&self, target: Target) -> Result { + pub(crate) fn build(&self, target: &Target, release: bool) -> Result { let target_str = target.to_string(); - let mut args = vec!["build", "--release", "--target", &target_str]; + let mut args = vec!["build", "--target", &target_str]; + if release { + args.push("--release"); + } if !target.composition_js() { args.push("--no-default-features"); } @@ -49,12 +54,7 @@ impl CargoRunner { } } self.cargo_exec(&args, Some(env))?; - Ok(self - .rover_package_directory - .join("target") - .join(&target_str) - .join("release") - .join("rover")) + Ok(self.get_bin_path(target, release)) } pub(crate) fn lint(&self) -> Result<()> { @@ -87,17 +87,24 @@ impl CargoRunner { Ok(()) } + pub(crate) fn get_bin_path(&self, target: &Target, release: bool) -> Utf8PathBuf { + let mut path = self.cargo_package_directory.clone(); + path.push("target"); + path.push(target.to_string()); + if release { + path.push("release") + } else { + path.push("debug") + } + path.push("rover"); + path + } + fn cargo_exec( &self, args: &[&str], env: Option>, ) -> Result { - utils::exec( - "cargo", - args, - &self.rover_package_directory, - self.verbose, - env, - ) + self.runner.exec(args, &self.cargo_package_directory, env) } } diff --git a/xtask/src/tools/git.rs b/xtask/src/tools/git.rs new file mode 100644 index 000000000..e2e6edc69 --- /dev/null +++ b/xtask/src/tools/git.rs @@ -0,0 +1,40 @@ +use crate::tools::Runner; + +use std::convert::TryFrom; + +use anyhow::{Context, Result}; +use assert_fs::TempDir; +use camino::Utf8PathBuf; + +pub(crate) struct GitRunner { + temp_dir_path: Utf8PathBuf, + runner: Runner, + + // we store _temp_dir here since its Drop implementation deletes the directory + _temp_dir: TempDir, +} + +impl GitRunner { + pub(crate) fn new(verbose: bool) -> Result { + let runner = Runner::new("git", verbose)?; + let temp_dir = TempDir::new().with_context(|| "Could not create temp directory")?; + let temp_dir_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf()) + .with_context(|| "Temp directory was not valid Utf-8")?; + + Ok(GitRunner { + runner, + temp_dir_path, + _temp_dir: temp_dir, + }) + } + + pub(crate) fn clone_supergraph_demo(&self) -> Result { + let repo_name = "supergraph-demo"; + let repo_url = format!("https://github.com/apollographql/{}", repo_name); + self.runner + .exec(&["clone", &repo_url], &self.temp_dir_path, None)?; + + let repo_path = self.temp_dir_path.join(repo_name); + Ok(repo_path) + } +} diff --git a/xtask/src/tools/make.rs b/xtask/src/tools/make.rs new file mode 100644 index 000000000..ef68dfd2b --- /dev/null +++ b/xtask/src/tools/make.rs @@ -0,0 +1,54 @@ +use std::collections::HashMap; + +use crate::tools::Runner; +use crate::utils::CommandOutput; + +use anyhow::{anyhow, Context, Result}; +use camino::{Utf8Path, Utf8PathBuf}; + +pub(crate) struct MakeRunner { + runner: Runner, + rover_exe: Utf8PathBuf, +} + +impl MakeRunner { + pub(crate) fn new(verbose: bool, rover_exe: Utf8PathBuf) -> Result { + let runner = Runner::new("make", verbose)?; + + Ok(MakeRunner { runner, rover_exe }) + } + + pub(crate) fn test_supergraph_demo(&self, base_dir: &Utf8Path) -> Result<()> { + let mut env = HashMap::new(); + env.insert("ROVER_BIN".to_string(), self.rover_exe.to_string()); + let output = self.runner.exec(&["ci"], base_dir, Some(env))?; + assert_demo_includes(&output) + .with_context(|| "There were problems with the output of 'make ci'.") + } +} + +fn assert_demo_includes(output: &CommandOutput) -> Result<()> { + let necessary_stdout = vec!["🚀 Graph Router ready at http://localhost:4000/"]; + let necessary_stderr = vec!["allProducts", "Removing network"]; + + let mut missing_strings = Vec::with_capacity(necessary_stderr.len() + necessary_stdout.len()); + for necessary_string in necessary_stdout { + if !output.stdout.contains(necessary_string) { + missing_strings.push(necessary_string); + } + } + for necessary_string in necessary_stderr { + if !output.stderr.contains(necessary_string) { + missing_strings.push(necessary_string); + } + } + + if missing_strings.is_empty() { + Ok(()) + } else { + Err(anyhow!( + "The output from 'make` is missing the following strings: {:?}", + missing_strings + )) + } +} diff --git a/xtask/src/tools/mod.rs b/xtask/src/tools/mod.rs new file mode 100644 index 000000000..457e34b45 --- /dev/null +++ b/xtask/src/tools/mod.rs @@ -0,0 +1,13 @@ +mod cargo; +mod git; +mod make; +mod npm; +mod runner; +mod strip; + +pub(crate) use cargo::CargoRunner; +pub(crate) use git::GitRunner; +pub(crate) use make::MakeRunner; +pub(crate) use npm::NpmRunner; +pub(crate) use runner::Runner; +pub(crate) use strip::StripRunner; diff --git a/xtask/src/commands/prep/npm.rs b/xtask/src/tools/npm.rs similarity index 54% rename from xtask/src/commands/prep/npm.rs rename to xtask/src/tools/npm.rs index 3d662f50e..a5a3bb239 100644 --- a/xtask/src/commands/prep/npm.rs +++ b/xtask/src/tools/npm.rs @@ -3,55 +3,54 @@ use camino::Utf8PathBuf; use std::str; -use crate::utils::{self, CommandOutput, PKG_VERSION}; - -/// prepares our npm installer package for release -/// by default this runs on every build and does all the steps -/// if the machine has npm installed. -/// these steps are only _required_ when running in release mode -pub(crate) fn prepare_package(verbose: bool) -> Result<()> { - let npm_installer = NpmInstaller::new(verbose)?; - - npm_installer - .update_dependency_tree() - .with_context(|| "Could not update the dependency tree.")?; - - npm_installer - .update_version() - .with_context(|| "Could not update Rover's version in package.json.")?; - - npm_installer - .install_dependencies() - .with_context(|| "Could not install dependencies.")?; - - npm_installer - .publish_dry_run() - .with_context(|| "Publish dry-run failed.")?; - - Ok(()) -} - -struct NpmInstaller { - rover_package_directory: Utf8PathBuf, - verbose: bool, +use crate::{ + tools::Runner, + utils::{self, CommandOutput, PKG_VERSION}, +}; + +pub(crate) struct NpmRunner { + runner: Runner, + npm_package_directory: Utf8PathBuf, } -impl NpmInstaller { - fn new(verbose: bool) -> Result { - let rover_package_directory = utils::project_root()?.join("installers").join("npm"); +impl NpmRunner { + pub(crate) fn new(verbose: bool) -> Result { + let runner = Runner::new("npm", verbose)?; + let npm_package_directory = utils::project_root()?.join("installers").join("npm"); - if rover_package_directory.exists() { + if npm_package_directory.exists() { Ok(Self { - rover_package_directory, - verbose, + runner, + npm_package_directory, }) } else { Err(anyhow!( "Rover's npm installer package does not seem to be located here:\n{}", - &rover_package_directory + &npm_package_directory )) } } + + /// prepares our npm installer package for release + /// by default this runs on every build and does all the steps + /// if the machine has npm installed. + /// these steps are only _required_ when running in release mode + pub(crate) fn prepare_package(&self) -> Result<()> { + self.update_dependency_tree() + .with_context(|| "Could not update the dependency tree.")?; + + self.update_version() + .with_context(|| "Could not update Rover's version in package.json.")?; + + self.install_dependencies() + .with_context(|| "Could not install dependencies.")?; + + self.publish_dry_run() + .with_context(|| "Publish dry-run failed.")?; + + Ok(()) + } + fn update_dependency_tree(&self) -> Result<()> { self.npm_exec(&["update"])?; Ok(()) @@ -77,13 +76,7 @@ impl NpmInstaller { } fn npm_exec(&self, args: &[&str]) -> Result { - utils::exec( - "npm", - args, - &self.rover_package_directory, - self.verbose, - None, - ) + self.runner.exec(args, &self.npm_package_directory, None) } } diff --git a/xtask/src/tools/runner.rs b/xtask/src/tools/runner.rs new file mode 100644 index 000000000..90754032f --- /dev/null +++ b/xtask/src/tools/runner.rs @@ -0,0 +1,87 @@ +use crate::utils::{self, CommandOutput}; + +use anyhow::{anyhow, Context, Result}; +use camino::{Utf8Path, Utf8PathBuf}; +use which::which; + +use std::collections::HashMap; +use std::convert::TryInto; +use std::process::{Command, Output}; +use std::str; + +pub(crate) struct Runner { + verbose: bool, + tool_name: String, + tool_exe: Utf8PathBuf, +} + +impl Runner { + pub(crate) fn new(tool_name: &str, verbose: bool) -> Result { + let tool_exe = which(tool_name).with_context(|| { + format!( + "You must have {} installed to run this command.", + &tool_name + ) + })?; + Ok(Runner { + verbose, + tool_name: tool_name.to_string(), + tool_exe: tool_exe.try_into()?, + }) + } + + pub(crate) fn exec( + &self, + args: &[&str], + directory: &Utf8Path, + env: Option>, + ) -> Result { + let full_command = format!("`{} {}`", &self.tool_name, args.join(" ")); + utils::info(&format!("running {}", &full_command)); + + let mut command = Command::new(&self.tool_exe); + command.current_dir(directory).args(args); + self.set_command_env(&mut command, env); + let output = command.output()?; + self.handle_command_output(output) + .with_context(|| format!("Encountered an issue while executing {}", &full_command)) + } + + fn set_command_env(&self, command: &mut Command, env: Option>) { + if let Some(env) = env { + for (key, value) in env { + command.env(&key, &value); + } + } + } + + fn handle_command_output(&self, output: Output) -> Result { + let command_was_successful = output.status.success(); + let stdout = str::from_utf8(&output.stdout) + .context("Command's stdout was not valid UTF-8.")? + .to_string(); + let stderr = str::from_utf8(&output.stderr) + .context("Command's stderr was not valid UTF-8.")? + .to_string(); + if self.verbose || !command_was_successful { + if !stderr.is_empty() { + eprintln!("{}", &stderr); + } + if !stdout.is_empty() { + println!("{}", &stdout); + } + } + + if command_was_successful { + Ok(CommandOutput { + stdout, + stderr, + _output: output, + }) + } else if let Some(exit_code) = output.status.code() { + Err(anyhow!("Exited with status code {}", exit_code)) + } else { + Err(anyhow!("Terminated by a signal.")) + } + } +} diff --git a/xtask/src/commands/dist/strip.rs b/xtask/src/tools/strip.rs similarity index 54% rename from xtask/src/commands/dist/strip.rs rename to xtask/src/tools/strip.rs index 94d1e65d9..5f960a9a3 100644 --- a/xtask/src/commands/dist/strip.rs +++ b/xtask/src/tools/strip.rs @@ -1,31 +1,27 @@ use anyhow::Result; use camino::Utf8PathBuf; +use crate::tools::Runner; use crate::utils; pub(crate) struct StripRunner { + runner: Runner, rover_executable: Utf8PathBuf, - verbose: bool, } impl StripRunner { - pub(crate) fn new(rover_executable: Utf8PathBuf, verbose: bool) -> Self { - StripRunner { + pub(crate) fn new(rover_executable: Utf8PathBuf, verbose: bool) -> Result { + let runner = Runner::new("strip", verbose)?; + Ok(StripRunner { + runner, rover_executable, - verbose, - } + }) } pub(crate) fn run(&self) -> Result<()> { let project_root = utils::project_root()?; - let rover_executable = self.rover_executable.to_string(); - utils::exec( - "strip", - &[&rover_executable], - &project_root, - self.verbose, - None, - )?; + self.runner + .exec(&[&self.rover_executable.to_string()], &project_root, None)?; Ok(()) } } diff --git a/xtask/src/utils.rs b/xtask/src/utils.rs index f6a2043f3..142f08492 100644 --- a/xtask/src/utils.rs +++ b/xtask/src/utils.rs @@ -3,15 +3,8 @@ use anyhow::{anyhow, Context, Result}; use camino::Utf8PathBuf; use cargo_metadata::MetadataCommand; use lazy_static::lazy_static; -use which::which; -use std::{ - collections::HashMap, - convert::TryFrom, - env, - process::{Command, Output}, - str, -}; +use std::{convert::TryFrom, env, process::Output, str}; const MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR"); @@ -48,66 +41,8 @@ pub(crate) fn project_root() -> Result { Ok(root_dir.to_path_buf()) } -pub(crate) fn exec( - command_name: &str, - args: &[&str], - directory: &Utf8PathBuf, - verbose: bool, - env: Option>, -) -> Result { - let command_path = which(command_name).with_context(|| { - format!( - "You must have {} installed to run this command.", - &command_name - ) - })?; - let full_command = format!("`{} {}`", command_name, args.join(" ")); - info(&format!("running {}", &full_command)); - let mut command = Command::new(command_path); - command.current_dir(directory).args(args); - - if let Some(env) = env { - for (key, value) in env { - command.env(&key, &value); - } - } - - let output = command.output()?; - let command_was_successful = output.status.success(); - let stdout = str::from_utf8(&output.stdout) - .context("Command's stdout was not valid UTF-8.")? - .to_string(); - let stderr = str::from_utf8(&output.stderr) - .context("Command's stderr was not valid UTF-8.")? - .to_string(); - if verbose || !command_was_successful { - if !stderr.is_empty() { - eprintln!("{}", &stderr); - } - if !stdout.is_empty() { - println!("{}", &stdout); - } - } - - if command_was_successful { - Ok(CommandOutput { - _stdout: stdout, - stderr, - _output: output, - }) - } else if let Some(exit_code) = output.status.code() { - Err(anyhow!( - "{} exited with status code {}", - &full_command, - exit_code - )) - } else { - Err(anyhow!("{} was terminated by a signal.", &full_command)) - } -} - pub(crate) struct CommandOutput { - pub(crate) _stdout: String, + pub(crate) stdout: String, pub(crate) stderr: String, pub(crate) _output: Output, }