diff --git a/src/cli.rs b/src/cli.rs index a6428308c4..0bafd56350 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -190,7 +190,11 @@ impl Rover { Command::Fed2(command) => command.run(self.get_client_config()?), Command::Supergraph(command) => { command - .run(self.get_install_override_path()?, self.get_client_config()?) + .run( + self.get_install_override_path()?, + self.get_client_config()?, + self.output_opts.output_file.clone(), + ) .await } Command::Docs(command) => command.run(), diff --git a/src/command/dev/compose.rs b/src/command/dev/compose.rs index 22de88644f..e11bc2fb7b 100644 --- a/src/command/dev/compose.rs +++ b/src/command/dev/compose.rs @@ -70,6 +70,7 @@ impl ComposeRunner { self.override_install_path.clone(), self.client_config.clone(), supergraph_config, + None, ) .await, ); diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index 165984ecb8..715f58d541 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -10,6 +10,7 @@ use apollo_federation_types::{ use camino::Utf8PathBuf; use clap::{Args, Parser}; use serde::Serialize; +use std::io::Read; use rover_client::shared::GraphRef; use rover_client::RoverClientError; @@ -112,6 +113,7 @@ impl Compose { &self, override_install_path: Option, client_config: StudioClientConfig, + output_file: Option, ) -> RoverResult { let mut supergraph_config = get_supergraph_config( &self.opts.supergraph_config_source.graph_ref, @@ -124,8 +126,13 @@ impl Compose { // WARNING: remove this unwrap .unwrap(); - self.compose(override_install_path, client_config, &mut supergraph_config) - .await + self.compose( + override_install_path, + client_config, + &mut supergraph_config, + output_file, + ) + .await } pub async fn compose( @@ -133,9 +140,15 @@ impl Compose { override_install_path: Option, client_config: StudioClientConfig, supergraph_config: &mut SupergraphConfig, + output_file: Option, ) -> RoverResult { let output = self - .exec(override_install_path, client_config, supergraph_config) + .exec( + override_install_path, + client_config, + supergraph_config, + output_file, + ) .await?; Ok(RoverOutput::CompositionResult(output)) } @@ -145,6 +158,7 @@ impl Compose { override_install_path: Option, client_config: StudioClientConfig, supergraph_config: &mut SupergraphConfig, + output_file: Option, ) -> RoverResult { // first, grab the _actual_ federation version from the config we just resolved // (this will always be `Some` as long as we have created with `resolve_supergraph_yaml` so it is safe to unwrap) @@ -186,33 +200,60 @@ impl Compose { &federation_version ); - let output = Command::new(&exe) - .args(["compose", yaml_path.as_ref()]) - .output() - .context("Failed to execute command")?; - let stdout = str::from_utf8(&output.stdout) - .with_context(|| format!("Could not parse output of `{} compose`", &exe))?; - - match serde_json::from_str::(stdout) { - Ok(build_result) => match build_result { - Ok(build_output) => Ok(CompositionOutput { - hints: build_output.hints, - supergraph_sdl: build_output.supergraph_sdl, - federation_version: Some(federation_version.to_string()), - }), - Err(build_errors) => Err(RoverError::from(RoverClientError::BuildErrors { - source: build_errors, - num_subgraphs, - })), - }, - Err(bad_json) => Err(anyhow!("{}", bad_json)) - .with_context(|| anyhow!("{} compose output: {}", &exe, stdout)) - .with_context(|| anyhow!("Output from `{} compose` was malformed.", &exe)) - .map_err(|e| { - let mut error = RoverError::new(e); - error.set_suggestion(RoverErrorSuggestion::SubmitIssue); - error - }), + // Whether we use stdout or a file dependson whether the the `--output` option was used + let content = match output_file { + // If it was, we use a file in the supergraph binary; this cuts down the overall time + // it takes to do composition when we're working on really large compositions, but it + // carries with it the assumption that stdout is superfluous + Some(filepath) => { + Command::new(&exe) + .args(["compose", yaml_path.as_ref(), &filepath.to_string()]) + .output() + .context("Failed to execute command")?; + + let mut composition_file = std::fs::File::open(filepath.to_string()).unwrap(); + let mut content: String = String::new(); + composition_file.read_to_string(&mut content).unwrap(); + content + } + // When we aren't using `--output`, we dump the composition directly to stdout + None => { + let output = Command::new(&exe) + .args(["compose", yaml_path.as_ref()]) + .output() + .context("Failed to execute command")?; + + let content = str::from_utf8(&output.stdout) + .with_context(|| format!("Could not parse output of `{} compose`", &exe))?; + content.to_string() + } + }; + + // Make sure the composition is well-formed + let composition = match serde_json::from_str::(&content) { + Ok(res) => res, + Err(err) => { + return Err(anyhow!("{}", err)) + .with_context(|| anyhow!("{} compose output: {}", &exe, content)) + .with_context(|| anyhow!("Output from `{} compose` was malformed.", &exe)) + .map_err(|e| { + let mut error = RoverError::new(e); + error.set_suggestion(RoverErrorSuggestion::SubmitIssue); + error + }) + } + }; + + match composition { + Ok(build_output) => Ok(CompositionOutput { + hints: build_output.hints, + supergraph_sdl: build_output.supergraph_sdl, + federation_version: Some(federation_version.to_string()), + }), + Err(build_errors) => Err(RoverError::from(RoverClientError::BuildErrors { + source: build_errors, + num_subgraphs, + })), } } diff --git a/src/command/supergraph/mod.rs b/src/command/supergraph/mod.rs index 1a18f0afb8..0edf8a2b35 100644 --- a/src/command/supergraph/mod.rs +++ b/src/command/supergraph/mod.rs @@ -28,10 +28,15 @@ impl Supergraph { &self, override_install_path: Option, client_config: StudioClientConfig, + output_file: Option, ) -> RoverResult { match &self.command { Command::Fetch(command) => command.run(client_config).await, - Command::Compose(command) => command.run(override_install_path, client_config).await, + Command::Compose(command) => { + command + .run(override_install_path, client_config, output_file) + .await + } } } } diff --git a/src/options/output.rs b/src/options/output.rs index a179c05e4f..8f4221fcde 100644 --- a/src/options/output.rs +++ b/src/options/output.rs @@ -85,7 +85,7 @@ pub struct OutputOpts { /// Specify a file to write Rover's output to #[arg(long = "output", short = 'o', global = true, value_parser = Self::parse_absolute_path)] - output_file: Option, + pub output_file: Option, } impl OutputOpts { diff --git a/src/utils/supergraph_config.rs b/src/utils/supergraph_config.rs index d5f18cef9b..fb5338dfda 100644 --- a/src/utils/supergraph_config.rs +++ b/src/utils/supergraph_config.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use anyhow::anyhow; use apollo_federation_types::build::{BuildError, BuildErrors, SubgraphDefinition}; use apollo_federation_types::config::{ @@ -5,7 +7,6 @@ use apollo_federation_types::config::{ }; use apollo_parser::{cst, Parser}; use futures::future::join_all; -use std::str::FromStr; use rover_client::blocking::{GraphQLClient, StudioClient}; use rover_client::operations::subgraph;