diff --git a/.circleci/config.yml b/.circleci/config.yml index 4d28c439d..2eb428677 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -397,11 +397,11 @@ commands: default: "" steps: - when: - condition: + condition: <<: *tag_matches_prerelease steps: - - run: - name: Create draft GitHub prerelease + - run: + name: Create GitHub prerelease command: | gh release create << parameters.release_tag >> \ --prerelease \ @@ -419,8 +419,8 @@ commands: condition: <<: *tag_matches_prerelease steps: - - run: - name: Create draft GitHub release + - run: + name: Create GitHub release command: | gh release create << parameters.release_tag >> \ --title << parameters.release_tag >> \ diff --git a/Cargo.lock b/Cargo.lock index 277a14915..ba55d6f07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -329,9 +329,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.61" +version = "0.3.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a905d892734eea339e896738c14b9afce22b5318f64b951e70bf3844419b01" +checksum = "091bcdf2da9950f96aa522681ce805e6857f6ca8df73833d35736ab2dc78e152" dependencies = [ "addr2line", "cc", @@ -475,9 +475,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.7.1" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" +checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" [[package]] name = "byteorder" @@ -973,9 +973,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.28" +version = "0.8.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" +checksum = "a74ea89a0a1b98f6332de42c95baff457ada66d1cb4030f9ff151b2041a1c746" dependencies = [ "cfg-if", ] @@ -1027,6 +1027,22 @@ dependencies = [ "instant", ] +[[package]] +name = "fed-types" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "fed-types" +version = "0.1.0" +source = "git+https://github.com/apollographql/rover?branch=avery/build-rover-fed-bin#7ad43c62b5297b1ac92a9b906298d54fa0bbdcbf" +dependencies = [ + "serde", +] + [[package]] name = "filetime" version = "0.2.15" @@ -1389,9 +1405,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c06815895acec637cd6ed6e9662c935b866d20a106f8361892893a7d9234964" +checksum = "7fd819562fcebdac5afc5c113c3ec36f902840b70fd4fc458799c8ce4607ae55" dependencies = [ "bytes", "fnv", @@ -1408,15 +1424,15 @@ dependencies = [ [[package]] name = "harmonizer" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985a164be21ccfe513b6df031ac69bdf611fca469452bbfb0d442cda40a646f0" +version = "0.33.4-0" +source = "git+https://github.com/EverlastingBugstopper/federation?branch=avery/harmonizer-cli#9be40bc150f00f02312bf3ae42cf2aad5bc388a0" dependencies = [ - "anyhow", "deno_core", + "fed-types 0.1.0 (git+https://github.com/apollographql/rover?branch=avery/build-rover-fed-bin)", "serde", "serde_json", - "thiserror", + "supergraph-config 0.1.0 (git+https://github.com/apollographql/rover?branch=avery/build-rover-fed-bin)", + "which", ] [[package]] @@ -1469,9 +1485,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "399c583b2979440c60be0821a6199eca73bc3c8dcd9d070d75ac726e2c6186e5" +checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" dependencies = [ "bytes", "http", @@ -1526,9 +1542,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.13" +version = "0.14.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d1cfb9e4f68655fa04c01f59edb405b6074a0f7118ea881e5026e4a1cd8593" +checksum = "2b91bb1f221b6ea1f1e4371216b70f40748774c2fb5971b450c07773fb92d26b" dependencies = [ "bytes", "futures-channel", @@ -1611,9 +1627,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] @@ -1745,9 +1761,9 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2f96d100e1cf1929e7719b7edb3b90ab5298072638fccd77be9ce942ecdfce" +checksum = "869d572136620d55835903746bcb5cdc54cb2851fd0aeec53220b4bb65ef3013" [[package]] name = "libgit2-sys" @@ -1862,9 +1878,9 @@ checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[package]] name = "minimad" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8957f240ecb82a4e699bcf4db189fe8a7f5aa68b9e6d5abf829c62a9ee4630ed" +checksum = "cd37b2e65fbd459544194d8f52ed84027e031684335a062c708774c09d172b0b" dependencies = [ "once_cell", ] @@ -1881,9 +1897,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" dependencies = [ "libc", "log", @@ -1971,9 +1987,9 @@ dependencies = [ [[package]] name = "object" -version = "0.26.2" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2" +checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" dependencies = [ "memchr", ] @@ -2174,9 +2190,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c9b1041b4387893b91ee6746cddfc28516aff326a3519fb2adf820932c5e6cb" +checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" [[package]] name = "polling" @@ -2193,9 +2209,9 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ca011bd0129ff4ae15cd04c4eef202cadf6c51c21e47aba319b4e0501db741" +checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" [[package]] name = "precomputed-hash" @@ -2435,9 +2451,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51c732d463dd300362ffb44b7b125f299c23d2990411a4253824630ebc7467fb" +checksum = "66d2927ca2f685faf0fc620ac4834690d29e7abb153add10f5812eef20b5e280" dependencies = [ "async-compression", "base64", @@ -2486,7 +2502,7 @@ dependencies = [ [[package]] name = "rover" -version = "0.3.0" +version = "0.4.0" dependencies = [ "ansi_term 0.12.1", "anyhow", @@ -2500,6 +2516,7 @@ dependencies = [ "chrono", "console 0.15.0", "crossterm", + "fed-types 0.1.0 (git+https://github.com/apollographql/rover?branch=avery/build-rover-fed-bin)", "git-url-parse", "git2", "harmonizer", @@ -2523,6 +2540,7 @@ dependencies = [ "structopt", "strum 0.22.0", "strum_macros 0.22.0", + "supergraph-config 0.1.0", "termimad", "timber", "toml", @@ -2537,6 +2555,7 @@ dependencies = [ "backoff", "camino", "chrono", + "fed-types 0.1.0 (git+https://github.com/apollographql/rover?branch=avery/build-rover-fed-bin)", "git-url-parse", "git2", "graphql_client", @@ -2559,6 +2578,20 @@ dependencies = [ "uuid", ] +[[package]] +name = "rover-fed" +version = "0.1.0" +dependencies = [ + "anyhow", + "camino", + "harmonizer", + "serde", + "serde_json", + "structopt", + "supergraph-config 0.1.0", + "tracing", +] + [[package]] name = "rust-argon2" version = "0.8.3" @@ -2908,9 +2941,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "structopt" -version = "0.3.23" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf9d950ef167e25e0bdb073cf1d68e9ad2795ac826f2f3f59647817cf23c0bfa" +checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c" dependencies = [ "clap", "lazy_static", @@ -2919,9 +2952,9 @@ dependencies = [ [[package]] name = "structopt-derive" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134d838a2c9943ac3125cf6df165eda53493451b719f3255b2a26b85f772d0ba" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" dependencies = [ "heck", "proc-macro-error", @@ -2966,6 +2999,34 @@ dependencies = [ "syn", ] +[[package]] +name = "supergraph-config" +version = "0.1.0" +dependencies = [ + "assert_fs", + "camino", + "fed-types 0.1.0 (git+https://github.com/apollographql/rover?branch=avery/build-rover-fed-bin)", + "log", + "serde", + "serde_yaml", + "thiserror", + "url", +] + +[[package]] +name = "supergraph-config" +version = "0.1.0" +source = "git+https://github.com/apollographql/rover?branch=avery/build-rover-fed-bin#7ad43c62b5297b1ac92a9b906298d54fa0bbdcbf" +dependencies = [ + "camino", + "fed-types 0.1.0 (git+https://github.com/apollographql/rover?branch=avery/build-rover-fed-bin)", + "log", + "serde", + "serde_yaml", + "thiserror", + "url", +] + [[package]] name = "syn" version = "1.0.80" @@ -3057,9 +3118,9 @@ dependencies = [ [[package]] name = "termimad" -version = "0.16.2" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cae919a0f56a978584712d3fbb21d60c9952fad28c872634590d5a201c79860" +checksum = "d88221817ef21964ccccf7d8ccc35fed3bae3066a177d1fcda72d694a016a47d" dependencies = [ "crossbeam", "crossterm", @@ -3080,9 +3141,9 @@ dependencies = [ [[package]] name = "termtree" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78fbf2dd23e79c28ccfa2472d3e6b3b189866ffef1aeb91f17c2d968b6586378" +checksum = "76565a2f8df1d2170b5c365aa39d0623fd93fec20545edde299233cea82d0f16" [[package]] name = "textwrap" diff --git a/Cargo.toml b/Cargo.toml index 4cbde7864..c8acbd1b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,12 +11,17 @@ license = "MIT" name = "rover" readme = "README.md" repository = "https://github.com/apollographql/rover/" -version = "0.3.0" +version = "0.4.0" +default-run = "rover" [[bin]] name = "rover" path = "src/bin/rover.rs" +[[bin]] +name = "rover-fed" +path = "crates/rover-fed/src/main.rs" + [workspace] members = [".", "xtask", "crates/*", "installers/binstall"] @@ -36,6 +41,7 @@ houston = { path = "./crates/houston" } robot-panic = { path = "./crates/robot-panic" } rover-client = { path = "./crates/rover-client" } sdl-encoder = { path = "./crates/sdl-encoder" } +supergraph-config = { path = "./crates/supergraph-config" } sputnik = { path = "./crates/sputnik" } timber = { path = "./crates/timber" } @@ -48,9 +54,10 @@ camino = { version = "1", features = ["serde1"] } chrono = "0.4" console = "0.15" crossterm = "0.21" +fed-types = { git = "https://github.com/apollographql/rover", branch = "avery/build-rover-fed-bin" } git-url-parse = "0.3" git2 = { version = "0.13", default-features = false, features = ["vendored-openssl"] } -harmonizer = { version = "0.33.0", optional = true } +harmonizer = { git = "https://github.com/EverlastingBugstopper/federation", branch = "avery/harmonizer-cli", optional = true } heck = "0.3" lazycell = "1" opener = "0.5" diff --git a/crates/fed-types/Cargo.toml b/crates/fed-types/Cargo.toml new file mode 100644 index 000000000..ab7347d32 --- /dev/null +++ b/crates/fed-types/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "fed-types" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1", features = ["derive"] } + +[dev-dependencies] +serde_json = "1" \ No newline at end of file diff --git a/crates/rover-client/src/shared/build_errors.rs b/crates/fed-types/src/build_error.rs similarity index 72% rename from crates/rover-client/src/shared/build_errors.rs rename to crates/fed-types/src/build_error.rs index 1e7e66c8c..17d28d38b 100644 --- a/crates/rover-client/src/shared/build_errors.rs +++ b/crates/fed-types/src/build_error.rs @@ -1,28 +1,27 @@ use std::{ error::Error, fmt::{self, Display}, - iter::FromIterator, }; use serde::{ser::SerializeSeq, Deserialize, Serialize, Serializer}; #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] pub struct BuildError { - message: String, + message: Option, code: Option, r#type: BuildErrorType, } impl BuildError { - pub fn composition_error(message: String, code: Option) -> BuildError { + pub fn composition_error(code: Option, message: Option) -> BuildError { BuildError { - message, code, + message, r#type: BuildErrorType::Composition, } } - pub fn get_message(&self) -> String { + pub fn get_message(&self) -> Option { self.message.clone() } @@ -39,12 +38,15 @@ pub enum BuildErrorType { impl Display for BuildError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(code) = &self.code { - write!(f, "{}: ", code)?; - } else { - write!(f, "UNKNOWN: ")?; + write!( + f, + "{}", + self.code.as_ref().map_or("UNKNOWN", String::as_str) + )?; + if let Some(message) = &self.message { + write!(f, ": {}", message)?; } - write!(f, "{}", &self.message) + Ok(()) } } @@ -100,8 +102,27 @@ impl BuildErrors { impl Display for BuildErrors { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for build_error in &self.build_errors { - writeln!(f, "{}", build_error)?; + let num_failures = self.build_errors.len(); + if num_failures == 0 + || (num_failures == 1 + && self.build_errors[0].code.is_none() + && self.build_errors[0].message.is_none()) + { + writeln!(f, "Something went wrong! No build errors were recorded, but we also build a valid supergraph SDL.")?; + } else { + let length_message = if num_failures == 1 { + "1 build error".to_string() + } else { + format!("{} build errors", num_failures) + }; + writeln!( + f, + "Encountered {} while trying to build the supergraph.", + &length_message + )?; + for build_error in &self.build_errors { + writeln!(f, "{}", build_error)?; + } } Ok(()) } @@ -146,8 +167,8 @@ mod tests { #[test] fn it_can_serialize_some_build_errors() { let build_errors: BuildErrors = vec![ - BuildError::composition_error("wow".to_string(), None), - BuildError::composition_error("boo".to_string(), Some("BOO".to_string())), + BuildError::composition_error(None, Some("wow".to_string())), + BuildError::composition_error(Some("BOO".to_string()), Some("boo".to_string())), ] .into(); @@ -159,13 +180,13 @@ mod tests { let expected_value = json!([ { - "code": null, "message": "wow", + "code": null, "type": "composition" }, { - "code": "BOO", "message": "boo", + "code": "BOO", "type": "composition" } ]); diff --git a/crates/fed-types/src/lib.rs b/crates/fed-types/src/lib.rs new file mode 100644 index 000000000..67d22af4e --- /dev/null +++ b/crates/fed-types/src/lib.rs @@ -0,0 +1,5 @@ +mod build_error; +mod subgraph_definition; + +pub use build_error::{BuildError, BuildErrors}; +pub use subgraph_definition::SubgraphDefinition; diff --git a/crates/fed-types/src/subgraph_definition.rs b/crates/fed-types/src/subgraph_definition.rs new file mode 100644 index 000000000..863a6de86 --- /dev/null +++ b/crates/fed-types/src/subgraph_definition.rs @@ -0,0 +1,34 @@ +use serde::Serialize; + +/// The `SubgraphDefinition` represents everything we need to know about a +/// subgraph for its GraphQL runtime responsibilities. +#[derive(Debug, Clone, PartialEq, Serialize)] +pub struct SubgraphDefinition { + /// The name of the subgraph. We use this name internally to + /// in the representation of the composed schema and for designations + /// within the human-readable QueryPlan. + pub name: String, + + /// The routing/runtime URL where the subgraph can be found that will + /// be able to fulfill the requests it is responsible for. + pub url: String, + + /// The Schema Definition Language (SDL) containing the type definitions + /// for a subgraph. + pub sdl: String, +} + +impl SubgraphDefinition { + /// Create a new SubgraphDefinition + pub fn new, U: Into, S: Into>( + name: N, + url: U, + sdl: S, + ) -> SubgraphDefinition { + SubgraphDefinition { + name: name.into(), + url: url.into(), + sdl: sdl.into(), + } + } +} diff --git a/crates/rover-client/Cargo.toml b/crates/rover-client/Cargo.toml index cea997d78..9f4276d42 100644 --- a/crates/rover-client/Cargo.toml +++ b/crates/rover-client/Cargo.toml @@ -8,7 +8,8 @@ version = "0.0.0" [dependencies] # workspace deps -houston = {path = "../houston"} +houston = { path = "../houston" } +fed-types = { git = "https://github.com/apollographql/rover", branch = "avery/build-rover-fed-bin" } # crates.io deps backoff = "0.3" diff --git a/crates/rover-client/src/error.rs b/crates/rover-client/src/error.rs index 82b9f91a2..fcd450d27 100644 --- a/crates/rover-client/src/error.rs +++ b/crates/rover-client/src/error.rs @@ -1,6 +1,8 @@ use thiserror::Error; -use crate::shared::{BuildErrors, CheckResponse, GraphRef}; +use crate::shared::{CheckResponse, GraphRef}; + +use fed_types::BuildErrors; /// RoverClientError represents all possible failures that can occur during a client request. #[derive(Error, Debug)] diff --git a/crates/rover-client/src/operations/subgraph/check/runner.rs b/crates/rover-client/src/operations/subgraph/check/runner.rs index b446ca7e9..f2de0e20b 100644 --- a/crates/rover-client/src/operations/subgraph/check/runner.rs +++ b/crates/rover-client/src/operations/subgraph/check/runner.rs @@ -4,9 +4,11 @@ use crate::operations::{ config::is_federated::{self, IsFederatedInput}, subgraph::check::types::MutationResponseData, }; -use crate::shared::{BuildError, CheckResponse, GraphRef, SchemaChange}; +use crate::shared::{CheckResponse, GraphRef, SchemaChange}; use crate::RoverClientError; +use fed_types::BuildError; + use graphql_client::*; type Timestamp = String; @@ -104,8 +106,8 @@ fn get_check_response_from_data( let mut build_errors = Vec::with_capacity(num_failures); for query_composition_error in query_composition_errors { build_errors.push(BuildError::composition_error( - query_composition_error.message, query_composition_error.code, + Some(query_composition_error.message), )); } Err(RoverClientError::SubgraphBuildErrors { diff --git a/crates/rover-client/src/operations/subgraph/delete/runner.rs b/crates/rover-client/src/operations/subgraph/delete/runner.rs index 9acfd9ed4..6826df350 100644 --- a/crates/rover-client/src/operations/subgraph/delete/runner.rs +++ b/crates/rover-client/src/operations/subgraph/delete/runner.rs @@ -1,8 +1,10 @@ use crate::blocking::StudioClient; use crate::operations::subgraph::delete::types::*; -use crate::shared::{BuildError, BuildErrors, GraphRef}; +use crate::shared::GraphRef; use crate::RoverClientError; +use fed_types::{BuildError, BuildErrors}; + use graphql_client::*; #[derive(GraphQLQuery)] @@ -49,7 +51,7 @@ fn build_response(response: MutationComposition) -> SubgraphDeleteResponse { .filter_map(|error| { error .as_ref() - .map(|e| BuildError::composition_error(e.message.clone(), e.code.clone())) + .map(|e| BuildError::composition_error(Some(e.message.clone()), e.code.clone())) }) .collect(); @@ -129,8 +131,8 @@ mod tests { parsed, SubgraphDeleteResponse { build_errors: vec![ - BuildError::composition_error("wow".to_string(), None), - BuildError::composition_error("boo".to_string(), Some("BOO".to_string())) + BuildError::composition_error(Some("wow".to_string()), None), + BuildError::composition_error(Some("boo".to_string()), Some("BOO".to_string())) ] .into(), supergraph_was_updated: false, diff --git a/crates/rover-client/src/operations/subgraph/delete/types.rs b/crates/rover-client/src/operations/subgraph/delete/types.rs index 86052896e..b3fac3b90 100644 --- a/crates/rover-client/src/operations/subgraph/delete/types.rs +++ b/crates/rover-client/src/operations/subgraph/delete/types.rs @@ -1,11 +1,10 @@ -use crate::{ - operations::subgraph::delete::runner::subgraph_delete_mutation, - shared::{BuildErrors, GraphRef}, -}; +use crate::{operations::subgraph::delete::runner::subgraph_delete_mutation, shared::GraphRef}; pub(crate) type MutationComposition = subgraph_delete_mutation::SubgraphDeleteMutationServiceRemoveImplementingServiceAndTriggerComposition; pub(crate) type MutationVariables = subgraph_delete_mutation::Variables; +use fed_types::BuildErrors; + use serde::Serialize; #[cfg(test)] diff --git a/crates/rover-client/src/operations/subgraph/publish/runner.rs b/crates/rover-client/src/operations/subgraph/publish/runner.rs index c525f2791..aef516f87 100644 --- a/crates/rover-client/src/operations/subgraph/publish/runner.rs +++ b/crates/rover-client/src/operations/subgraph/publish/runner.rs @@ -5,10 +5,13 @@ use crate::operations::{ config::is_federated::{self, IsFederatedInput}, graph::variant, }; -use crate::shared::{BuildError, BuildErrors, GraphRef}; +use crate::shared::{GraphRef}; use crate::RoverClientError; + use graphql_client::*; +use fed_types::{BuildError, BuildErrors}; + #[derive(GraphQLQuery)] // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema @@ -94,7 +97,7 @@ fn build_response(publish_response: UpdateResponse) -> SubgraphPublishResponse { .filter_map(|error| { error .as_ref() - .map(|e| BuildError::composition_error(e.message.clone(), e.code.clone())) + .map(|e| BuildError::composition_error(e.code.clone(), Some(e.message.clone()))) }) .collect(); @@ -140,12 +143,12 @@ mod tests { api_schema_hash: Some("5gf564".to_string()), build_errors: vec![ BuildError::composition_error( - "[Accounts] User -> build error".to_string(), - None + None, + Some("[Accounts] User -> build error".to_string()) ), BuildError::composition_error( - "[Products] Product -> another one".to_string(), - Some("ERROR".to_string()) + Some("ERROR".to_string()), + Some("[Products] Product -> another one".to_string()) ) ] .into(), @@ -198,8 +201,8 @@ mod tests { SubgraphPublishResponse { api_schema_hash: None, build_errors: vec![BuildError::composition_error( - "[Accounts] -> Things went really wrong".to_string(), - None + None, + Some("[Accounts] -> Things went really wrong".to_string()) )] .into(), supergraph_was_updated: false, diff --git a/crates/rover-client/src/operations/subgraph/publish/types.rs b/crates/rover-client/src/operations/subgraph/publish/types.rs index a948320c6..4817f3e9c 100644 --- a/crates/rover-client/src/operations/subgraph/publish/types.rs +++ b/crates/rover-client/src/operations/subgraph/publish/types.rs @@ -1,11 +1,13 @@ use super::runner::subgraph_publish_mutation; -use crate::shared::{BuildErrors, GitContext, GraphRef}; +use crate::shared::{GitContext, GraphRef}; pub(crate) type ResponseData = subgraph_publish_mutation::ResponseData; pub(crate) type MutationVariables = subgraph_publish_mutation::Variables; pub(crate) type UpdateResponse = subgraph_publish_mutation::SubgraphPublishMutationServiceUpsertImplementingServiceAndTriggerComposition; +use fed_types::BuildErrors; + type SchemaInput = subgraph_publish_mutation::PartialSchemaInput; type GitContextInput = subgraph_publish_mutation::GitContextInput; diff --git a/crates/rover-client/src/operations/supergraph/fetch/runner.rs b/crates/rover-client/src/operations/supergraph/fetch/runner.rs index 8374c6d51..25ba8f636 100644 --- a/crates/rover-client/src/operations/supergraph/fetch/runner.rs +++ b/crates/rover-client/src/operations/supergraph/fetch/runner.rs @@ -1,8 +1,10 @@ use crate::blocking::StudioClient; use crate::operations::supergraph::fetch::SupergraphFetchInput; -use crate::shared::{BuildError, BuildErrors, FetchResponse, GraphRef, Sdl, SdlType}; +use crate::shared::{FetchResponse, GraphRef, Sdl, SdlType}; use crate::RoverClientError; +use fed_types::{BuildError, BuildErrors}; + use graphql_client::*; // I'm not sure where this should live long-term @@ -70,7 +72,7 @@ fn get_supergraph_sdl_from_response_data( let build_errors: BuildErrors = most_recent_composition_publish .errors .into_iter() - .map(|error| BuildError::composition_error(error.message, error.code)) + .map(|error| BuildError::composition_error(error.code, Some(error.message))) .collect(); Err(RoverClientError::NoSupergraphBuilds { graph_ref, @@ -153,12 +155,12 @@ mod tests { fn get_schema_from_response_data_errs_on_no_schema_tag() { let build_errors = vec![ BuildError::composition_error( - "Unknown type \"Unicorn\".".to_string(), Some("UNKNOWN_TYPE".to_string()), + Some("Unknown type \"Unicorn\".".to_string()), ), BuildError::composition_error( - "Type Query must define one or more fields.".to_string(), None, + Some("Type Query must define one or more fields.".to_string()), ), ]; let build_errors_json = json!([ diff --git a/crates/rover-client/src/shared/mod.rs b/crates/rover-client/src/shared/mod.rs index e09603fb1..3f5e94241 100644 --- a/crates/rover-client/src/shared/mod.rs +++ b/crates/rover-client/src/shared/mod.rs @@ -1,10 +1,8 @@ -mod build_errors; mod check_response; mod fetch_response; mod git_context; mod graph_ref; -pub use build_errors::{BuildError, BuildErrors}; pub use check_response::{ ChangeSeverity, CheckConfig, CheckResponse, SchemaChange, ValidationPeriod, }; diff --git a/crates/rover-fed/Cargo.toml b/crates/rover-fed/Cargo.toml new file mode 100644 index 000000000..981385ec5 --- /dev/null +++ b/crates/rover-fed/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "rover-fed" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1" +camino = "1" +harmonizer = { git = "https://github.com/EverlastingBugstopper/federation", branch = "avery/harmonizer-cli" } +structopt = "0.3" +supergraph-config = { path = "../supergraph-config" } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tracing = "0.1" \ No newline at end of file diff --git a/crates/rover-fed/src/cli.rs b/crates/rover-fed/src/cli.rs new file mode 100644 index 000000000..2ec470f93 --- /dev/null +++ b/crates/rover-fed/src/cli.rs @@ -0,0 +1,33 @@ +use structopt::StructOpt; + +use crate::command::Command; + +#[derive(Debug, StructOpt)] +#[structopt( + name = "rover-fed", + about = "A utility for composing multiple subgraphs into a supergraph" +)] +pub struct RoverFed { + #[structopt(subcommand)] + command: Command, + + /// Print output as JSON. + #[structopt(long, global = true)] + json: bool, +} + +impl RoverFed { + pub fn run(&self) -> Result<(), anyhow::Error> { + let output = match &self.command { + Command::Compose(command) => command.run(), + }?; + + if self.json { + println!("{}", serde_json::json!(output)); + } else { + println!("{}", output.supergraph_sdl) + } + + Ok(()) + } +} diff --git a/crates/rover-fed/src/command/compose.rs b/crates/rover-fed/src/command/compose.rs new file mode 100644 index 000000000..5955ddfd2 --- /dev/null +++ b/crates/rover-fed/src/command/compose.rs @@ -0,0 +1,22 @@ +use camino::Utf8PathBuf; +use structopt::StructOpt; + +use harmonizer::{harmonize, CompositionOutput}; +use supergraph_config::SupergraphConfig; + +#[derive(Debug, StructOpt)] +pub struct Compose { + /// The path to the fully resolved supergraph YAML. + /// + /// NOTE: Each subgraph entry MUST contain raw SDL + /// as the schema source. + config_file: Utf8PathBuf, +} + +impl Compose { + pub fn run(&self) -> Result { + let supergraph_config = SupergraphConfig::new_from_yaml_file(&self.config_file)?; + let subgraph_definitions = supergraph_config.get_subgraph_definitions()?; + Ok(harmonize(subgraph_definitions)?) + } +} diff --git a/crates/rover-fed/src/command/mod.rs b/crates/rover-fed/src/command/mod.rs new file mode 100644 index 000000000..3a273f099 --- /dev/null +++ b/crates/rover-fed/src/command/mod.rs @@ -0,0 +1,11 @@ +mod compose; + +pub use compose::Compose; + +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +pub enum Command { + /// Compose a supergraph from a fully resolved supergraph config YAML + Compose(Compose), +} diff --git a/crates/rover-fed/src/main.rs b/crates/rover-fed/src/main.rs new file mode 100644 index 000000000..2664bc956 --- /dev/null +++ b/crates/rover-fed/src/main.rs @@ -0,0 +1,11 @@ +mod cli; +pub(crate) mod command; + +use cli::RoverFed; + +use structopt::StructOpt; + +fn main() -> Result<(), anyhow::Error> { + let app = RoverFed::from_args(); + app.run() +} diff --git a/crates/supergraph-config/Cargo.toml b/crates/supergraph-config/Cargo.toml new file mode 100644 index 000000000..c9a8ef9eb --- /dev/null +++ b/crates/supergraph-config/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "supergraph-config" +version = "0.1.0" +authors = ["Apollo Developers "] +edition = "2021" +license ="MIT" + +[dependencies] +camino = { version = "1", features = [ "serde1" ] } +fed-types = { git = "https://github.com/apollographql/rover", branch = "avery/build-rover-fed-bin" } +log = "0.4" +serde = "1" +serde_yaml = "0.8" +thiserror = "1" +url = { version = "2", features = [ "serde" ] } + +[dev-dependencies] +assert_fs = "1" \ No newline at end of file diff --git a/crates/supergraph-config/src/error.rs b/crates/supergraph-config/src/error.rs new file mode 100644 index 000000000..8b5ee6bed --- /dev/null +++ b/crates/supergraph-config/src/error.rs @@ -0,0 +1,16 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Could not parse supergraph config: {message}.")] + InvalidConfiguration { message: String }, + + #[error("File \"{file_path}\" not found: {message}.")] + MissingFile { file_path: String, message: String }, + + #[error("Config for subgraph(s) {subgraph_names} are not fully resolved. name, routing_url, and sdl must be present.")] + SubgraphsNotResolved { subgraph_names: String }, + + #[error("No subgraphs were found in the supergraph config.")] + NoSubgraphsFound, +} diff --git a/crates/supergraph-config/src/lib.rs b/crates/supergraph-config/src/lib.rs new file mode 100644 index 000000000..5930b7557 --- /dev/null +++ b/crates/supergraph-config/src/lib.rs @@ -0,0 +1,8 @@ +mod error; +mod subgraph; +mod supergraph; + +pub use error::Error; +pub type Result = std::result::Result; +pub use subgraph::{SchemaSource, SubgraphConfig}; +pub use supergraph::SupergraphConfig; diff --git a/crates/supergraph-config/src/subgraph.rs b/crates/supergraph-config/src/subgraph.rs new file mode 100644 index 000000000..20f2c0660 --- /dev/null +++ b/crates/supergraph-config/src/subgraph.rs @@ -0,0 +1,45 @@ +use camino::Utf8PathBuf; +use serde::{Deserialize, Serialize}; +use url::Url; + +/// Config for a single [subgraph](https://www.apollographql.com/docs/federation/subgraphs/) +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SubgraphConfig { + /// The routing URL for the subgraph. + /// This will appear in supergraph SDL and + /// instructs the graph router to send all requests + /// for this subgraph to this URL. + pub routing_url: Option, + + /// The location of the subgraph's SDL + pub schema: SchemaSource, +} + +impl SubgraphConfig { + /// Returns SDL from the configuration file if it exists. + /// Returns None if the configuration does not include raw SDL. + pub fn get_sdl(&self) -> Option { + if let SchemaSource::Sdl { sdl } = &self.schema { + Some(sdl.to_owned()) + } else { + None + } + } +} + +/// Options for getting SDL: +/// the graph registry, a file, or an introspection URL. +/// +/// NOTE: Introspection strips all comments and directives +/// from the SDL. +#[derive(Debug, Clone, Serialize, Deserialize)] +// this is untagged, meaning its fields will be flattened into the parent +// struct when de/serialized. There is no top level `schema_source` +// in the configuration. +#[serde(untagged)] +pub enum SchemaSource { + File { file: Utf8PathBuf }, + SubgraphIntrospection { subgraph_url: Url }, + Subgraph { graphref: String, subgraph: String }, + Sdl { sdl: String }, +} diff --git a/crates/supergraph-config/src/supergraph.rs b/crates/supergraph-config/src/supergraph.rs new file mode 100644 index 000000000..ef87ef214 --- /dev/null +++ b/crates/supergraph-config/src/supergraph.rs @@ -0,0 +1,184 @@ +use crate::{Error, Result, SubgraphConfig}; + +use fed_types::SubgraphDefinition; + +use camino::Utf8PathBuf; +use serde::{Deserialize, Serialize}; + +use std::{collections::BTreeMap, fs}; + +/// The configuration for a single supergraph +/// composed of multiple subgraphs. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SupergraphConfig { + // Store config in a BTreeMap, as HashMap is non-deterministic. + subgraphs: BTreeMap, +} + +impl SupergraphConfig { + /// Create a new SupergraphConfig from a YAML string in memory. + pub fn new_from_yaml(yaml: &str) -> Result { + let parsed_config = + serde_yaml::from_str(yaml).map_err(|e| Error::InvalidConfiguration { + message: e.to_string(), + })?; + + log::debug!("{:?}", parsed_config); + + Ok(parsed_config) + } + + /// Create a new SupergraphConfig from a YAML file. + pub fn new_from_yaml_file>(config_path: P) -> Result { + let config_path: Utf8PathBuf = config_path.into(); + let supergraph_yaml = fs::read_to_string(&config_path).map_err(|e| Error::MissingFile { + file_path: config_path.to_string(), + message: e.to_string(), + })?; + + let parsed_config = SupergraphConfig::new_from_yaml(&supergraph_yaml)?; + + Ok(parsed_config) + } + + /// Returns a Vec of resolved subgraphs, if and only if they are all resolved. + /// Resolved in this sense means that each subgraph config includes + /// a name, a URL, and raw SDL. + pub fn get_subgraph_definitions(&self) -> Result> { + let mut subgraph_definitions = Vec::new(); + let mut unresolved_subgraphs = Vec::new(); + for (subgraph_name, subgraph_config) in &self.subgraphs { + if let Some(sdl) = subgraph_config.get_sdl() { + if let Some(routing_url) = &subgraph_config.routing_url { + subgraph_definitions.push(SubgraphDefinition::new( + subgraph_name, + routing_url, + sdl, + )); + } else { + unresolved_subgraphs.push(subgraph_name); + } + } else { + unresolved_subgraphs.push(subgraph_name); + } + } + if !unresolved_subgraphs.is_empty() { + Err(Error::SubgraphsNotResolved { + subgraph_names: format!("{:?}", &unresolved_subgraphs), + }) + } else if subgraph_definitions.is_empty() { + Err(Error::NoSubgraphsFound) + } else { + Ok(subgraph_definitions) + } + } +} + +impl From> for SupergraphConfig { + fn from(input: Vec) -> Self { + let mut subgraphs = BTreeMap::new(); + for subgraph_definition in input { + subgraphs.insert( + subgraph_definition.name, + SubgraphConfig { + routing_url: Some(subgraph_definition.url), + schema: crate::SchemaSource::Sdl { + sdl: subgraph_definition.sdl, + }, + }, + ); + } + Self { subgraphs } + } +} + +// implement IntoIterator so you can do: +// for (subgraph_name, subgraph_metadata) in supergraph_config.into_iter() { ... } +impl IntoIterator for SupergraphConfig { + type Item = (String, SubgraphConfig); + type IntoIter = std::collections::btree_map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.subgraphs.into_iter() + } +} + +#[cfg(test)] +mod tests { + use super::SupergraphConfig; + + use assert_fs::TempDir; + use camino::Utf8PathBuf; + use std::convert::TryFrom; + use std::fs; + + #[test] + fn it_can_parse_valid_config() { + let raw_good_yaml = r#"subgraphs: + films: + routing_url: https://films.example.com + schema: + file: ./good-films.graphql + people: + routing_url: https://people.example.com + schema: + file: ./good-people.graphql +"#; + + assert!(SupergraphConfig::new_from_yaml(raw_good_yaml).is_ok()); + } + + #[test] + fn it_can_parse_valid_config_from_fs() { + let raw_good_yaml = r#"subgraphs: + films: + routing_url: https://films.example.com + schema: + file: ./good-films.graphql + people: + routing_url: https://people.example.com + schema: + file: ./good-people.graphql +"#; + + let tmp_home = TempDir::new().unwrap(); + let mut config_path = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); + config_path.push("config.yaml"); + fs::write(&config_path, raw_good_yaml).unwrap(); + + assert!(SupergraphConfig::new_from_yaml_file(&config_path).is_ok()); + } + + #[test] + fn it_can_parse_valid_config_with_introspection() { + let raw_good_yaml = r#"subgraphs: + films: + routing_url: https://films.example.com + schema: + file: ./films.graphql + people: + schema: + subgraph_url: https://people.example.com + reviews: + schema: + graphref: mygraph@current + subgraph: reviews +"#; + + assert!(SupergraphConfig::new_from_yaml(raw_good_yaml).is_ok()); + } + + #[test] + fn it_errors_on_invalid_config() { + let raw_bad_yaml = r#"subgraphs: + films: + routing_______url: https://films.example.com + schemaaaa: + file:: ./good-films.graphql + people: + routing____url: https://people.example.com + schema_____file: ./good-people.graphql"#; + + assert!(SupergraphConfig::new_from_yaml(raw_bad_yaml).is_err()) + } +} diff --git a/installers/binstall/src/error.rs b/installers/binstall/src/error.rs index dc6f92353..2c7d1f9e8 100644 --- a/installers/binstall/src/error.rs +++ b/installers/binstall/src/error.rs @@ -24,4 +24,8 @@ pub enum InstallerError { /// A specified path was not valid UTF-8 #[error(transparent)] PathNotUtf8(#[from] camino::FromPathBufError), + + /// Attempted to install a plugin without first installing the main tool + #[error("You cannot install {} without first installing {}.", plugin, tool)] + PluginRequiresTool { plugin: String, tool: String }, } diff --git a/installers/binstall/src/install.rs b/installers/binstall/src/install.rs index 4ea6e51b9..c77c45492 100644 --- a/installers/binstall/src/install.rs +++ b/installers/binstall/src/install.rs @@ -17,18 +17,6 @@ pub struct Installer { impl Installer { /// Installs the executable and returns the location it was installed. pub fn install(&self) -> Result, InstallerError> { - let install_path = self.do_install()?; - - Ok(install_path) - } - - /// Gets the location the executable will be installed to - pub fn get_bin_dir_path(&self) -> Result { - let bin_dir = self.get_base_dir_path()?.join("bin"); - Ok(bin_dir) - } - - fn do_install(&self) -> Result, InstallerError> { let bin_destination = self.get_bin_path()?; if !self.force_install @@ -38,18 +26,60 @@ impl Installer { return Ok(None); } - tracing::debug!("Creating directory for binary"); self.create_bin_dir()?; eprintln!("Writing binary to {}", &bin_destination); self.write_bin_to_fs()?; - tracing::debug!("Adding binary to PATH"); self.add_binary_to_path()?; Ok(Some(bin_destination)) } + /// This command requires that the binary already exists, + /// Downloads a plugin tarball from a URL, extracts the binary, + /// and puts it in the `bin` directory for the main tool + pub fn install_plugin( + &self, + plugin_name: &str, + plugin_tarball_url: &str, + ) -> Result, InstallerError> { + if self.get_bin_dir_path()?.exists() { + // The main binary already exists in a standard location + let plugin_bin_destination = self.get_plugin_bin_path(plugin_name)?; + if !self.force_install + && plugin_bin_destination.exists() + && !self.should_overwrite(&plugin_bin_destination)? + { + return Ok(None); + } + let plugin_bin_path = self.extract_plugin_tarball(plugin_tarball_url)?; + self.write_plugin_bin_to_fs(plugin_name, &plugin_bin_path)?; + Ok(Some(plugin_bin_destination)) + } else { + Err(InstallerError::PluginRequiresTool { + plugin: plugin_name.to_string(), + tool: self.binary_name.to_string(), + }) + } + + // if main exe is not already installed { + // error + // } else { + // download tarball + // extract binary from tarball + // print warning about new license? + // .rover/bin should already exist + // move binary to `.rover/bin/rover-fed` + // } + } + + /// Gets the location the executable will be installed to + pub fn get_bin_dir_path(&self) -> Result { + let bin_dir = self.get_base_dir_path()?.join("bin"); + Ok(bin_dir) + } + pub(crate) fn get_base_dir_path(&self) -> Result { let base_dir = if let Some(base_dir) = &self.override_install_path { Ok(base_dir.to_owned()) @@ -60,6 +90,7 @@ impl Installer { } fn create_bin_dir(&self) -> Result<(), InstallerError> { + tracing::debug!("Creating directory for binary"); fs::create_dir_all(self.get_bin_dir_path()?)?; Ok(()) } @@ -71,6 +102,13 @@ impl Installer { .with_extension(env::consts::EXE_EXTENSION)) } + fn get_plugin_bin_path(&self, plugin_name: &str) -> Result { + Ok(self + .get_bin_dir_path()? + .join(plugin_name) + .with_extension(env::consts::EXE_EXTENSION)) + } + fn write_bin_to_fs(&self) -> Result<(), InstallerError> { let bin_path = self.get_bin_path()?; tracing::debug!( @@ -85,6 +123,24 @@ impl Installer { Ok(()) } + fn write_plugin_bin_to_fs( + &self, + plugin_name: &str, + plugin_bin_path: &Utf8PathBuf, + ) -> Result<(), InstallerError> { + let plugin_destination = self.get_plugin_bin_path(plugin_name)?; + tracing::debug!( + "copying \"{}\" to \"{}\"", + plugin_bin_path, + &plugin_destination + ); + // attempt to remove the old binary + // but do not error if it doesn't exist. + let _ = fs::remove_file(&plugin_destination); + fs::copy(plugin_bin_path, &plugin_destination)?; + Ok(()) + } + fn should_overwrite(&self, destination: &Utf8PathBuf) -> Result { // If we're not attached to a TTY then we can't get user input, so there's // nothing to do except inform the user about the `-f` flag. @@ -109,13 +165,28 @@ impl Installer { } } + fn extract_plugin_tarball( + &self, + _plugin_tarball_url: &str, + ) -> Result { + std::process::Command::new("cargo") + .args(&["build", "--bin", "rover-fed"]) + .output() + .unwrap(); + Ok(Utf8PathBuf::from( + "/home/avery/work/rover/target/debug/rover-fed", + )) + } + #[cfg(windows)] fn add_binary_to_path(&self) -> Result<(), InstallerError> { + tracing::debug!("Adding binary to PATH"); crate::windows::add_binary_to_path(self) } #[cfg(not(windows))] fn add_binary_to_path(&self) -> Result<(), InstallerError> { + tracing::debug!("Adding binary to PATH"); crate::unix::add_binary_to_path(self) } } diff --git a/installers/binstall/src/lib.rs b/installers/binstall/src/lib.rs index 4aedc09d8..94ebd40f8 100644 --- a/installers/binstall/src/lib.rs +++ b/installers/binstall/src/lib.rs @@ -5,14 +5,16 @@ //! the existing installation in `PATH`, or to add a new directory //! for the binary to live in and add it to `PATH`. //! -//! This installer is run directly (probably by clicking on it) on Windows, -//! meaning it will pop up a console (as we're a console app). Output goes to -//! the console and users interact with it through the console. On Unix this is -//! intended to be run from a shell script (docs/installer/init.sh) which is -//! downloaded via curl/sh, and then the shell script downloads this executable -//! and runs it. +//! On Windows this is intended to be run from PowerShell +//! which is downloaded via iwr | iex. //! -//! This may get more complicated over time (self upates anyone?) but for now +//! On Unix this is intended to be run from a shell script +//! which is downloaded via curl | sh. +//! +//! Both the PowerShell script and the Unix script download this executable +//! and run it. +//! +//! This may get more complicated over time (self updates anyone?) but for now //! it's pretty simple! We're largely just moving over our currently running //! executable to a different path. diff --git a/src/command/install/mod.rs b/src/command/install/mod.rs index 293a4b941..0eb692425 100644 --- a/src/command/install/mod.rs +++ b/src/command/install/mod.rs @@ -13,10 +13,16 @@ use crate::{command::docs::shortlinks, utils::env::RoverEnvKey}; use std::convert::TryFrom; use std::env; +mod plugin; +use plugin::Plugin; + #[derive(Debug, Serialize, StructOpt)] pub struct Install { #[structopt(long = "force", short = "f")] force: bool, + + #[structopt(long, possible_values = &["rover-fed"], case_insensitive = true)] + plugin: Option, } impl Install { @@ -30,45 +36,63 @@ impl Install { override_install_path, executable_location, }; - let install_location = installer - .install() - .with_context(|| format!("could not install {}", &binary_name))?; - if install_location.is_some() { - let bin_dir_path = installer.get_bin_dir_path()?; - eprintln!("{} was successfully installed. Great!", &binary_name); + if let Some(plugin) = &self.plugin { + // TODO: print warning about license before install? + + let plugin_name = plugin.get_name(); + let install_location = installer + .install_plugin(&plugin_name, &plugin.get_tarball_url()) + .with_context(|| format!("Could not install {}", &plugin_name))?; - if !cfg!(windows) { - if let Some(path_var) = env::var_os("PATH") { - if !path_var - .to_string_lossy() - .to_string() - .contains(bin_dir_path.as_str()) - { - eprintln!("\nTo get started you need Rover's bin directory ({}) in your PATH environment variable. Next time you log in this will be done automatically.", &bin_dir_path); - if let Ok(shell_var) = env::var("SHELL") { - eprintln!( - "\nTo configure your current shell, you can run:\nexec {} -l", - &shell_var - ); + if install_location.is_some() { + eprintln!("{} was successfully installed. Great!", &plugin_name); + } else { + eprintln!("{} was not installed. To override the existing installation, you can pass the `--force` flag to the installer.", &plugin_name); + } + + Ok(RoverOutput::EmptySuccess) + } else { + let install_location = installer + .install() + .with_context(|| format!("could not install {}", &binary_name))?; + + if install_location.is_some() { + let bin_dir_path = installer.get_bin_dir_path()?; + eprintln!("{} was successfully installed. Great!", &binary_name); + + if !cfg!(windows) { + if let Some(path_var) = env::var_os("PATH") { + if !path_var + .to_string_lossy() + .to_string() + .contains(bin_dir_path.as_str()) + { + eprintln!("\nTo get started you need Rover's bin directory ({}) in your PATH environment variable. Next time you log in this will be done automatically.", &bin_dir_path); + if let Ok(shell_var) = env::var("SHELL") { + eprintln!( + "\nTo configure your current shell, you can run:\nexec {} -l", + &shell_var + ); + } } } } - } - // these messages are duplicated in `installers/npm/install.js` - // for the npm installer. - eprintln!( - "If you would like to disable Rover's anonymized usage collection, you can set {}=1", RoverEnvKey::TelemetryDisabled - ); - eprintln!( - "You can check out our documentation at {}.", - Cyan.normal().paint(shortlinks::get_url_from_slug("docs")) - ); - } else { - eprintln!("{} was not installed. To override the existing installation, you can pass the `--force` flag to the installer.", &binary_name); + // these messages are duplicated in `installers/npm/install.js` + // for the npm installer. + eprintln!( + "If you would like to disable Rover's anonymized usage collection, you can set {}=1", RoverEnvKey::TelemetryDisabled + ); + eprintln!( + "You can check out our documentation at {}.", + Cyan.normal().paint(shortlinks::get_url_from_slug("docs")) + ); + } else { + eprintln!("{} was not installed. To override the existing installation, you can pass the `--force` flag to the installer.", &binary_name); + } + Ok(RoverOutput::EmptySuccess) } - Ok(RoverOutput::EmptySuccess) } else { Err(anyhow!("Failed to get the current executable's path.").into()) } diff --git a/src/command/install/plugin.rs b/src/command/install/plugin.rs new file mode 100644 index 000000000..02edcec92 --- /dev/null +++ b/src/command/install/plugin.rs @@ -0,0 +1,38 @@ +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; +use structopt::StructOpt; + +#[derive(StructOpt, Debug, Serialize, Deserialize)] +#[structopt(rename_all = "kebab-case")] +pub(crate) enum Plugin { + RoverFed, +} + +impl Plugin { + pub fn get_name(&self) -> String { + match self { + Self::RoverFed => "rover-fed".to_string(), + } + } + + pub fn get_tarball_url(&self) -> String { + match self { + // TODO: make a url automatically by calling self.get_name() + // also probably need a way to override the version + Self::RoverFed => "https://github.com/apollographql/rover/releases/download/v0.2.0/rover-v0.2.0-x86_64-unknown-linux-gnu.tar.gz".to_string() + } + } +} + +impl FromStr for Plugin { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + let lowercase = s.to_lowercase(); + match lowercase.as_str() { + "rover-fed" => Ok(Plugin::RoverFed), + _ => Err(anyhow::anyhow!("Invalid plugin name.")), + } + } +} diff --git a/src/command/output.rs b/src/command/output.rs index 460ff5b42..d988899a6 100644 --- a/src/command/output.rs +++ b/src/command/output.rs @@ -430,9 +430,11 @@ mod tests { list::{SubgraphInfo, SubgraphUpdatedAt}, }, }, - shared::{BuildError, BuildErrors, ChangeSeverity, SchemaChange, Sdl}, + shared::{ChangeSeverity, SchemaChange, Sdl}, }; + use fed_types::{BuildError, BuildErrors}; + use crate::anyhow; use super::*; @@ -598,12 +600,12 @@ mod tests { supergraph_was_updated: false, build_errors: vec![ BuildError::composition_error( - "[Accounts] -> Things went really wrong".to_string(), Some("AN_ERROR_CODE".to_string()), + Some("[Accounts] -> Things went really wrong".to_string()), ), BuildError::composition_error( - "[Films] -> Something else also went wrong".to_string(), None, + Some("[Films] -> Something else also went wrong".to_string()), ), ] .into(), @@ -655,12 +657,12 @@ mod tests { }; let source = BuildErrors::from(vec![ BuildError::composition_error( - "[Accounts] -> Things went really wrong".to_string(), Some("AN_ERROR_CODE".to_string()), + Some("[Accounts] -> Things went really wrong".to_string()), ), BuildError::composition_error( - "[Films] -> Something else also went wrong".to_string(), None, + Some("[Films] -> Something else also went wrong".to_string()), ), ]); let actual_json: JsonOutput = @@ -890,12 +892,12 @@ mod tests { build_errors: vec![ BuildError::composition_error( - "[Accounts] -> Things went really wrong".to_string(), Some("AN_ERROR_CODE".to_string()), + Some("[Accounts] -> Things went really wrong".to_string()), ), BuildError::composition_error( - "[Films] -> Something else also went wrong".to_string(), None, + Some("[Films] -> Something else also went wrong".to_string()), ), ] .into(), @@ -1056,12 +1058,12 @@ mod tests { fn composition_error_message_json() { let source = BuildErrors::from(vec![ BuildError::composition_error( - "[Accounts] -> Things went really wrong".to_string(), Some("AN_ERROR_CODE".to_string()), + Some("[Accounts] -> Things went really wrong".to_string()), ), BuildError::composition_error( - "[Films] -> Something else also went wrong".to_string(), None, + Some("[Films] -> Something else also went wrong".to_string()), ), ]); let actual_json: JsonOutput = diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs index 55f3db01c..cc38ed016 100644 --- a/src/command/supergraph/compose/do_compose.rs +++ b/src/command/supergraph/compose/do_compose.rs @@ -1,15 +1,15 @@ -use crate::command::supergraph::config::{self, SchemaSource, SupergraphConfig}; use crate::utils::client::StudioClientConfig; use crate::{anyhow, command::RoverOutput, error::RoverError, Result, Suggestion}; +use supergraph_config::{SchemaSource, SupergraphConfig}; use rover_client::blocking::GraphQLClient; use rover_client::operations::subgraph::fetch::{self, SubgraphFetchInput}; use rover_client::operations::subgraph::introspect::{self, SubgraphIntrospectInput}; -use rover_client::shared::{BuildError, GraphRef}; +use rover_client::shared::GraphRef; use rover_client::RoverClientError; use camino::Utf8PathBuf; -use harmonizer::ServiceDefinition as SubgraphDefinition; +use fed_types::SubgraphDefinition; use serde::Serialize; use structopt::StructOpt; @@ -30,36 +30,16 @@ pub struct Compose { impl Compose { pub fn run(&self, client_config: StudioClientConfig) -> Result { - let supergraph_config = config::parse_supergraph_config(&self.config_path)?; - let subgraph_definitions = get_subgraph_definitions( - supergraph_config, - &self.config_path, - client_config, - &self.profile_name, - )?; + let subgraph_definitions = + get_subgraph_definitions(&self.config_path, client_config, &self.profile_name)?; - match harmonizer::harmonize(subgraph_definitions) { - Ok(core_schema) => Ok(RoverOutput::CoreSchema(core_schema)), - Err(harmonizer_composition_errors) => { - let mut build_errors = Vec::with_capacity(harmonizer_composition_errors.len()); - for harmonizer_composition_error in harmonizer_composition_errors { - if let Some(message) = &harmonizer_composition_error.message { - build_errors.push(BuildError::composition_error( - message.to_string(), - Some(harmonizer_composition_error.code().to_string()), - )); - } - } - Err(RoverError::new(RoverClientError::BuildErrors { - source: build_errors.into(), - })) - } - } + Ok(harmonizer::harmonize(subgraph_definitions) + .map(|output| RoverOutput::CoreSchema(output.supergraph_sdl)) + .map_err(|errs| RoverClientError::BuildErrors { source: errs })?) } } pub(crate) fn get_subgraph_definitions( - supergraph_config: SupergraphConfig, config_path: &Utf8PathBuf, client_config: StudioClientConfig, profile_name: &str, @@ -73,7 +53,9 @@ pub(crate) fn get_subgraph_definitions( err }; - for (subgraph_name, subgraph_data) in &supergraph_config.subgraphs { + let supergraph_config = SupergraphConfig::new_from_yaml_file(config_path)?; + + for (subgraph_name, subgraph_data) in supergraph_config.into_iter() { match &subgraph_data.schema { SchemaSource::File { file } => { let relative_schema_path = match config_path.parent() { @@ -159,6 +141,13 @@ pub(crate) fn get_subgraph_definitions( SubgraphDefinition::new(subgraph_name, url, &result.sdl.contents); subgraphs.push(subgraph_definition); } + SchemaSource::Sdl { sdl } => { + let url = &subgraph_data + .routing_url + .clone() + .ok_or_else(err_no_routing_url)?; + subgraphs.push(SubgraphDefinition::new(subgraph_name, url, sdl)) + } } } @@ -190,24 +179,17 @@ mod tests { let raw_good_yaml = r#"subgraphs: films: routing_url: https://films.example.com - schema: + schema: file: ./films-do-not-exist.graphql people: routing_url: https://people.example.com - schema: + schema: file: ./people-do-not-exist.graphql"#; let tmp_home = TempDir::new().unwrap(); let mut config_path = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); config_path.push("config.yaml"); fs::write(&config_path, raw_good_yaml).unwrap(); - let supergraph_config = config::parse_supergraph_config(&config_path).unwrap(); - assert!(get_subgraph_definitions( - supergraph_config, - &config_path, - get_studio_config(), - "profile" - ) - .is_err()) + assert!(get_subgraph_definitions(&config_path, get_studio_config(), "profile").is_err()) } #[test] @@ -215,11 +197,11 @@ mod tests { let raw_good_yaml = r#"subgraphs: films: routing_url: https://films.example.com - schema: + schema: file: ./films.graphql people: routing_url: https://people.example.com - schema: + schema: file: ./people.graphql"#; let tmp_home = TempDir::new().unwrap(); let mut config_path = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); @@ -230,14 +212,7 @@ mod tests { let people_path = tmp_dir.join("people.graphql"); fs::write(films_path, "there is something here").unwrap(); fs::write(people_path, "there is also something here").unwrap(); - let supergraph_config = config::parse_supergraph_config(&config_path).unwrap(); - assert!(get_subgraph_definitions( - supergraph_config, - &config_path, - get_studio_config(), - "profile" - ) - .is_ok()) + assert!(get_subgraph_definitions(&config_path, get_studio_config(), "profile").is_ok()) } #[test] @@ -245,11 +220,11 @@ mod tests { let raw_good_yaml = r#"subgraphs: films: routing_url: https://films.example.com - schema: + schema: file: ../../films.graphql people: routing_url: https://people.example.com - schema: + schema: file: ../../people.graphql"#; let tmp_home = TempDir::new().unwrap(); let tmp_dir = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); @@ -263,22 +238,16 @@ mod tests { let people_path = tmp_dir.join("people.graphql"); fs::write(films_path, "there is something here").unwrap(); fs::write(people_path, "there is also something here").unwrap(); - let supergraph_config = config::parse_supergraph_config(&config_path).unwrap(); - let subgraph_definitions = get_subgraph_definitions( - supergraph_config, - &config_path, - get_studio_config(), - "profile", - ) - .unwrap(); + let subgraph_definitions = + get_subgraph_definitions(&config_path, get_studio_config(), "profile").unwrap(); let film_subgraph = subgraph_definitions.get(0).unwrap(); let people_subgraph = subgraph_definitions.get(1).unwrap(); assert_eq!(film_subgraph.name, "films"); assert_eq!(film_subgraph.url, "https://films.example.com"); - assert_eq!(film_subgraph.type_defs, "there is something here"); + assert_eq!(film_subgraph.sdl, "there is something here"); assert_eq!(people_subgraph.name, "people"); assert_eq!(people_subgraph.url, "https://people.example.com"); - assert_eq!(people_subgraph.type_defs, "there is also something here"); + assert_eq!(people_subgraph.sdl, "there is also something here"); } } diff --git a/src/command/supergraph/config.rs b/src/command/supergraph/config.rs deleted file mode 100644 index dbbd70623..000000000 --- a/src/command/supergraph/config.rs +++ /dev/null @@ -1,115 +0,0 @@ -#[cfg(feature = "composition-js")] -use crate::{anyhow, Result}; - -#[cfg(feature = "composition-js")] -use std::fs; - -use camino::Utf8PathBuf; -use serde::{Deserialize, Serialize}; -use url::Url; - -use std::collections::BTreeMap; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub(crate) struct SupergraphConfig { - // Store config in a BTreeMap, as HashMap is non-deterministic. - pub(crate) subgraphs: BTreeMap, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub(crate) struct Subgraph { - pub(crate) routing_url: Option, - pub(crate) schema: SchemaSource, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub(crate) enum SchemaSource { - File { file: Utf8PathBuf }, - SubgraphIntrospection { subgraph_url: Url }, - Subgraph { graphref: String, subgraph: String }, -} - -#[cfg(feature = "composition-js")] -pub(crate) fn parse_supergraph_config(config_path: &Utf8PathBuf) -> Result { - let raw_supergraph_config = fs::read_to_string(config_path) - .map_err(|e| anyhow!("Could not read \"{}\": {}", config_path, e))?; - - let parsed_config = serde_yaml::from_str(&raw_supergraph_config) - .map_err(|e| anyhow!("Could not parse YAML from \"{}\": {}", config_path, e))?; - - tracing::debug!(?parsed_config); - - Ok(parsed_config) -} - -#[cfg(feature = "composition-js")] -#[cfg(test)] -mod tests { - use assert_fs::TempDir; - use camino::Utf8PathBuf; - use std::convert::TryFrom; - use std::fs; - - #[test] - fn it_can_parse_valid_config() { - let raw_good_yaml = r#"subgraphs: - films: - routing_url: https://films.example.com - schema: - file: ./good-films.graphql - people: - routing_url: https://people.example.com - schema: - file: ./good-people.graphql -"#; - let tmp_home = TempDir::new().unwrap(); - let mut config_path = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); - config_path.push("config.yaml"); - fs::write(&config_path, raw_good_yaml).unwrap(); - - let supergraph_config = super::parse_supergraph_config(&config_path); - if let Err(e) = supergraph_config { - panic!("{}", e) - } - } - #[test] - fn it_can_parse_valid_config_with_introspection() { - let raw_good_yaml = r#"subgraphs: - films: - routing_url: https://films.example.com - schema: - file: ./films.graphql - people: - schema: - subgraph_url: https://people.example.com - reviews: - schema: - graphref: mygraph@current - subgraph: reviews -"#; - let tmp_home = TempDir::new().unwrap(); - let mut config_path = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); - config_path.push("config.yaml"); - fs::write(&config_path, raw_good_yaml).unwrap(); - - super::parse_supergraph_config(&config_path).expect("Could not parse supergraph config"); - } - - #[test] - fn it_errors_on_invalid_config() { - let raw_bad_yaml = r#"subgraphs: - films: - routing_______url: https://films.example.com - schemaaaa: - file:: ./good-films.graphql - people: - routing____url: https://people.example.com - schema_____file: ./good-people.graphql"#; - let tmp_home = TempDir::new().unwrap(); - let mut config_path = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); - config_path.push("config.yaml"); - fs::write(&config_path, raw_bad_yaml).unwrap(); - assert!(super::parse_supergraph_config(&config_path).is_err()) - } -} diff --git a/src/command/supergraph/mod.rs b/src/command/supergraph/mod.rs index 51e2141a3..70a94173c 100644 --- a/src/command/supergraph/mod.rs +++ b/src/command/supergraph/mod.rs @@ -1,5 +1,4 @@ mod compose; -pub(crate) mod config; mod fetch; use serde::Serialize; diff --git a/src/error/mod.rs b/src/error/mod.rs index 2b47f640b..631d5ba09 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -17,7 +17,7 @@ use std::fmt::{self, Debug, Display}; pub use self::metadata::Suggestion; -use rover_client::shared::BuildErrors; +use fed_types::BuildErrors; /// A specialized `Error` type for Rover that wraps `anyhow` /// and provides some extra `Metadata` for end users depending diff --git a/xtask/src/commands/dist.rs b/xtask/src/commands/dist.rs index 6b6ee9f89..d2061c371 100644 --- a/xtask/src/commands/dist.rs +++ b/xtask/src/commands/dist.rs @@ -19,15 +19,22 @@ pub struct Dist { impl Dist { pub fn run(&self, verbose: bool) -> Result<()> { let mut cargo_runner = CargoRunner::new(verbose)?; - let binary_path = cargo_runner - .build(&self.target, true, self.version.as_ref()) - .with_context(|| "Could not build Rover.")?; + let bin_paths = vec![ + cargo_runner + .build_binary(&self.target, true, self.version.as_ref(), "rover") + .with_context(|| "Could not build Rover.")?, + cargo_runner + .build_binary(&self.target, true, self.version.as_ref(), "rover-fed") + .with_context(|| "Could not build rover-fed")?, + ]; if !cfg!(windows) { - let strip_runner = StripRunner::new(binary_path, verbose)?; - strip_runner - .run() - .with_context(|| "Could not strip symbols from Rover's binary")?; + for bin_path in bin_paths { + let strip_runner = StripRunner::new(bin_path.clone(), verbose)?; + strip_runner + .run() + .with_context(|| format!("Could not strip symbols from {}", &bin_path))?; + } } Ok(()) diff --git a/xtask/src/commands/integration_test.rs b/xtask/src/commands/integration_test.rs index bfa40ff1f..e6bc707d4 100644 --- a/xtask/src/commands/integration_test.rs +++ b/xtask/src/commands/integration_test.rs @@ -26,9 +26,11 @@ impl IntegrationTest { let git_runner = GitRunner::new(verbose)?; 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, None)?; + let make_runner = MakeRunner::new( + verbose, + cargo_runner.get_bin_path(&self.target, release, "rover")?, + )?; + cargo_runner.build_binary(&self.target, release, None, "rover")?; let repo_path = git_runner.clone_supergraph_demo(&self.org, &self.branch)?; make_runner.test_supergraph_demo(&repo_path)?; diff --git a/xtask/src/commands/package/mod.rs b/xtask/src/commands/package/mod.rs index fd721df8a..b51fdce60 100644 --- a/xtask/src/commands/package/mod.rs +++ b/xtask/src/commands/package/mod.rs @@ -42,11 +42,20 @@ impl Package { .run(true)?; } - let release_path = TARGET_DIR - .join(self.target.to_string()) - .join("release") - .join(RELEASE_BIN); + for bin in &["rover", "rover-fed"] { + self.create_tarball(bin)?; + } + + Ok(()) + } + fn create_tarball(&self, bin_name: &str) -> Result<()> { + let mut release_path = TARGET_DIR.join(self.target.to_string()).join("release"); + if cfg!(windows) { + release_path.push(format!("{}.exe", bin_name)); + } else { + release_path.push(bin_name) + } ensure!( release_path.exists(), "Could not find binary at: {}", @@ -63,7 +72,7 @@ impl Package { let output_path = if self.output.is_dir() { self.output.join(format!( "{}-v{}-{}.tar.gz", - PKG_PROJECT_NAME, *PKG_VERSION, self.target + bin_name, *PKG_VERSION, self.target )) } else { bail!("--output must be a path to a directory, not a file."); diff --git a/xtask/src/tools/cargo.rs b/xtask/src/tools/cargo.rs index 02cf033da..efaa28438 100644 --- a/xtask/src/tools/cargo.rs +++ b/xtask/src/tools/cargo.rs @@ -40,52 +40,55 @@ impl CargoRunner { self.env.insert(key, value) } - pub(crate) fn build( + pub(crate) fn build_binary( &mut self, target: &Target, release: bool, version: Option<&RoverVersion>, + binary: &str, ) -> Result { - if let Some(version) = version { - let git_runner = GitRunner::new(self.runner.verbose)?; - let repo_path = git_runner.checkout_rover_version(version.to_string().as_str())?; - let versioned_schema_url = format!( - "https://github.com/apollographql/rover/releases/download/{0}/rover-{0}-schema.graphql", - &version); - let max_version_not_supporting_env_var = RoverVersion::new(Version { - major: 0, - minor: 2, - patch: 0, - pre: Prerelease::new("beta.0")?, - build: BuildMetadata::EMPTY, - }); - self.set_path(repo_path.clone()); - self.git_runner = Some(git_runner); - - if version > &max_version_not_supporting_env_var { - self.env( - "APOLLO_GRAPHQL_SCHEMA_URL".to_string(), - versioned_schema_url, - ); - } else { - crate::info!("downloading schema from {}", &versioned_schema_url); - let schema_response = - reqwest::blocking::get(versioned_schema_url)?.error_for_status()?; - let schema_text = schema_response.text()?; - if !schema_text.contains("subgraph") { - anyhow!("This schema doesn't seem to contain any references to `subgraph`s. It's probably the wrong schema."); + if binary == "rover" { + if let Some(version) = version { + let git_runner = GitRunner::new(self.runner.verbose)?; + let repo_path = git_runner.checkout_rover_version(version.to_string().as_str())?; + let versioned_schema_url = format!( + "https://github.com/apollographql/rover/releases/download/{0}/rover-{0}-schema.graphql", + &version); + let max_version_not_supporting_env_var = RoverVersion::new(Version { + major: 0, + minor: 2, + patch: 0, + pre: Prerelease::new("beta.0")?, + build: BuildMetadata::EMPTY, + }); + self.set_path(repo_path.clone()); + self.git_runner = Some(git_runner); + + if version > &max_version_not_supporting_env_var { + self.env( + "APOLLO_GRAPHQL_SCHEMA_URL".to_string(), + versioned_schema_url, + ); + } else { + crate::info!("downloading schema from {}", &versioned_schema_url); + let schema_response = + reqwest::blocking::get(versioned_schema_url)?.error_for_status()?; + let schema_text = schema_response.text()?; + if !schema_text.contains("subgraph") { + anyhow!("This schema doesn't seem to contain any references to `subgraph`s. It's probably the wrong schema."); + } + let schema_dir = repo_path + .join("crates") + .join("rover-client") + .join(".schema"); + let _ = self.cargo_exec_with_target(target, vec!["build"], vec![], release); + fs::write(schema_dir.join("schema.graphql"), schema_text)?; } - let schema_dir = repo_path - .join("crates") - .join("rover-client") - .join(".schema"); - let _ = self.cargo_exec_with_target(target, vec!["build"], vec![], release); - fs::write(schema_dir.join("schema.graphql"), schema_text)?; } } - self.cargo_exec_with_target(target, vec!["build"], vec![], release)?; - let bin_path = self.get_bin_path(target, release)?; + self.cargo_exec_with_target(target, vec!["build", "--bin", binary], vec![], release)?; + let bin_path = self.get_bin_path(target, release, binary)?; crate::info!("successfully compiled to `{}`", &bin_path); Ok(bin_path) } @@ -142,7 +145,12 @@ impl CargoRunner { } } - pub(crate) fn get_bin_path(&self, target: &Target, release: bool) -> Result { + pub(crate) fn get_bin_path( + &self, + target: &Target, + release: bool, + bin: &str, + ) -> Result { let mut out_path = self.cargo_package_directory.clone(); let mut root_path = PKG_PROJECT_ROOT.clone(); @@ -167,7 +175,7 @@ impl CargoRunner { .with_context(|| "Could not copy build contents to local target directory")?; } - root_path.push("rover"); + root_path.push(bin); Ok(root_path) }