diff --git a/.cargo/config b/.cargo/config index 16f1e73ac..f2ea191eb 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,2 +1,5 @@ [build] -rustflags = ["--cfg", "tokio_unstable"] \ No newline at end of file +rustflags = ["--cfg", "tokio_unstable"] + +[alias] +xtask = "run --manifest-path ./xtask/Cargo.toml --" \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bd41a4a4d..7f321f945 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -75,6 +75,11 @@ jobs: override: true - uses: Swatinem/rust-cache@v1 + - name: Install Protoc + uses: arduino/setup-protoc@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Run cargo test (API) uses: actions-rs/cargo@v1 with: diff --git a/Cargo.lock b/Cargo.lock index cc901b32e..b1f35352d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -238,15 +238,6 @@ dependencies = [ "os_str_bytes", ] -[[package]] -name = "cmake" -version = "0.1.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a" -dependencies = [ - "cc", -] - [[package]] name = "color-eyre" version = "0.5.11" @@ -277,9 +268,10 @@ dependencies = [ [[package]] name = "console-api" -version = "0.3.0" +version = "0.4.0" dependencies = [ "prost", + "prost-build", "prost-types", "tonic", "tonic-build", @@ -1069,9 +1061,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bd5316aa8f5c82add416dfbc25116b84b748a21153f512917e8143640a71bbd" +checksum = "399c3c31cdec40583bb68f0b18403400d01ec4289c383aa047560439952c4dd7" dependencies = [ "bytes", "prost-derive", @@ -1079,13 +1071,11 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "328f9f29b82409216decb172d81e936415d21245befa79cd34c3f29d87d1c50b" +checksum = "7f835c582e6bd972ba8347313300219fed5bfa52caf175298d860b61ff6069bb" dependencies = [ "bytes", - "cfg-if", - "cmake", "heck", "itertools", "lazy_static", @@ -1101,9 +1091,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df35198f0777b75e9ff669737c6da5136b59dba33cf5a010a6d1cc4d56defc6f" +checksum = "7345d5f0e08c0536d7ac7229952590239e77abf0a0100a1b1d890add6ea96364" dependencies = [ "anyhow", "itertools", @@ -1114,9 +1104,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "926681c118ae6e512a3ccefd4abbe5521a14f4cc1e207356d4d00c0b7f2006fd" +checksum = "d30bc806a29b347314be074ff0608ef8e547286e8ea68b061a2fe55689edc01f" dependencies = [ "bytes", "prost", @@ -1528,9 +1518,9 @@ dependencies = [ [[package]] name = "tonic" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1a361140b1af3f548e0a5105126b3fc737542f6cd4947b66419c80be07db22" +checksum = "498f271adc46acce75d66f639e4d35b31b2394c295c82496727dafa16d465dd2" dependencies = [ "async-stream", "async-trait", @@ -1560,9 +1550,9 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d17087af5c80e5d5fc8ba9878e60258065a0a757e35efe7a05b7904bece1943" +checksum = "2fbcd2800e34e743b9ae795867d5f77b535d3a3be69fd731e39145719752df8c" dependencies = [ "prettyplease", "proc-macro2", @@ -1910,3 +1900,12 @@ name = "windows_x86_64_msvc" version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" + +[[package]] +name = "xtask" +version = "0.1.0" +dependencies = [ + "clap", + "color-eyre", + "tonic-build", +] diff --git a/Cargo.toml b/Cargo.toml index 0f289d821..153d99d70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "tokio-console", "console-subscriber", - "console-api" + "console-api", + "xtask" ] resolver = "2" diff --git a/console-api/Cargo.toml b/console-api/Cargo.toml index 009ed725d..f01d08a08 100644 --- a/console-api/Cargo.toml +++ b/console-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "console-api" -version = "0.3.0" +version = "0.4.0" license = "MIT" edition = "2021" rust-version = "1.58.0" @@ -29,19 +29,21 @@ keywords = [ transport = ["tonic-build/transport", "tonic/transport"] [dependencies] -tonic = { version = "0.7", default-features = false, features = [ +tonic = { version = "0.8", default-features = false, features = [ "prost", "codegen", "transport", ] } -prost = "0.10" -prost-types = "0.10" +prost = "0.11" +prost-types = "0.11" tracing-core = "0.1.17" [dev-dependencies] -tonic-build = { version = "0.7", default-features = false, features = [ +tonic-build = { version = "0.8", default-features = false, features = [ "prost", "transport" ] } +# explicit dep so we can get the version with fixed whitespace. +prost-build = "0.11.1" [package.metadata.docs.rs] all-features = true diff --git a/console-api/src/generated/google.protobuf.rs b/console-api/src/generated/google.protobuf.rs new file mode 100644 index 000000000..e69de29bb diff --git a/console-api/src/generated/rs.tokio.console.common.rs b/console-api/src/generated/rs.tokio.console.common.rs index d9a77c8d3..d651148e3 100644 --- a/console-api/src/generated/rs.tokio.console.common.rs +++ b/console-api/src/generated/rs.tokio.console.common.rs @@ -165,6 +165,18 @@ pub mod metadata { /// Indicates metadata is associated with an event. Event = 1, } + impl Kind { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Kind::Span => "SPAN", + Kind::Event => "EVENT", + } + } + } /// Describes the level of verbosity of a span or event. /// /// Corresponds to `Level` in the `tracing` crate. @@ -191,6 +203,21 @@ pub mod metadata { /// Designates very low priority, often extremely verbose, information. Trace = 4, } + impl Level { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Level::Error => "ERROR", + Level::Warn => "WARN", + Level::Info => "INFO", + Level::Debug => "DEBUG", + Level::Trace => "TRACE", + } + } + } } /// Contains stats about objects that can be polled. Currently these can be: /// - tasks that have been spawned diff --git a/console-api/src/generated/rs.tokio.console.instrument.rs b/console-api/src/generated/rs.tokio.console.instrument.rs index bf1f570ab..e39c9331c 100644 --- a/console-api/src/generated/rs.tokio.console.instrument.rs +++ b/console-api/src/generated/rs.tokio.console.instrument.rs @@ -30,7 +30,7 @@ pub struct ResumeRequest { /// - we can use one single timestamp for all the data /// - we can have all the new_metadata in one place /// - things such as async ops and resource ops do not make sense -/// on their own as they have relations to tasks and resources +/// on their own as they have relations to tasks and resources #[derive(Clone, PartialEq, ::prost::Message)] pub struct Update { /// The system time when this update was recorded. @@ -64,6 +64,7 @@ pub struct ResumeResponse { pub mod instrument_client { #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] use tonic::codegen::*; + use tonic::codegen::http::Uri; /// `InstrumentServer` implements `Instrument` as a service. #[derive(Debug, Clone)] pub struct InstrumentClient { @@ -84,19 +85,24 @@ pub mod instrument_client { where T: tonic::client::GrpcService, T::Error: Into, - T::ResponseBody: Default + Body + Send + 'static, + T::ResponseBody: Body + Send + 'static, ::Error: Into + Send, { pub fn new(inner: T) -> Self { let inner = tonic::client::Grpc::new(inner); Self { inner } } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } pub fn with_interceptor( inner: T, interceptor: F, ) -> InstrumentClient> where F: tonic::service::Interceptor, + T::ResponseBody: Default, T: tonic::codegen::Service< http::Request, Response = http::Response< @@ -109,19 +115,19 @@ pub mod instrument_client { { InstrumentClient::new(InterceptedService::new(inner, interceptor)) } - /// Compress requests with `gzip`. + /// Compress requests with the given encoding. /// /// This requires the server to support it otherwise it might respond with an /// error. #[must_use] - pub fn send_gzip(mut self) -> Self { - self.inner = self.inner.send_gzip(); + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); self } - /// Enable decompressing responses with `gzip`. + /// Enable decompressing responses. #[must_use] - pub fn accept_gzip(mut self) -> Self { - self.inner = self.inner.accept_gzip(); + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); self } /// Produces a stream of updates representing the behavior of the instrumented async runtime. @@ -258,8 +264,8 @@ pub mod instrument_server { #[derive(Debug)] pub struct InstrumentServer { inner: _Inner, - accept_compression_encodings: (), - send_compression_encodings: (), + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, } struct _Inner(Arc); impl InstrumentServer { @@ -283,6 +289,18 @@ pub mod instrument_server { { InterceptedService::new(Self::new(inner), interceptor) } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } } impl tonic::codegen::Service> for InstrumentServer where @@ -491,7 +509,7 @@ pub mod instrument_server { write!(f, "{:?}", self.0) } } - impl tonic::transport::NamedService for InstrumentServer { + impl tonic::server::NamedService for InstrumentServer { const NAME: &'static str = "rs.tokio.console.instrument.Instrument"; } } diff --git a/console-api/src/generated/rs.tokio.console.resources.rs b/console-api/src/generated/rs.tokio.console.resources.rs index 10c014758..714391461 100644 --- a/console-api/src/generated/rs.tokio.console.resources.rs +++ b/console-api/src/generated/rs.tokio.console.resources.rs @@ -80,6 +80,17 @@ pub mod resource { /// `TIMER` signals that this is a timer resource, e.g. waiting for a sleep to finish. Timer = 0, } + impl Known { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Known::Timer => "TIMER", + } + } + } /// Every resource is either a known kind or an other (unknown) kind. #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Kind { diff --git a/console-api/src/generated/rs.tokio.console.tasks.rs b/console-api/src/generated/rs.tokio.console.tasks.rs index 10915a6c5..6ff543bc9 100644 --- a/console-api/src/generated/rs.tokio.console.tasks.rs +++ b/console-api/src/generated/rs.tokio.console.tasks.rs @@ -47,7 +47,7 @@ pub struct TaskDetails { /// /// This is either: /// - the raw binary representation of a HdrHistogram.rs `Histogram` - /// serialized to binary in the V2 format (legacy) + /// serialized to binary in the V2 format (legacy) /// - a binary histogram plus details on outliers (current) #[prost(oneof="task_details::PollTimesHistogram", tags="3, 4")] pub poll_times_histogram: ::core::option::Option, @@ -58,7 +58,7 @@ pub mod task_details { /// /// This is either: /// - the raw binary representation of a HdrHistogram.rs `Histogram` - /// serialized to binary in the V2 format (legacy) + /// serialized to binary in the V2 format (legacy) /// - a binary histogram plus details on outliers (current) #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum PollTimesHistogram { @@ -125,6 +125,18 @@ pub mod task { /// (such as `tokio::task::spawn_blocking`). Blocking = 1, } + impl Kind { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Kind::Spawn => "SPAWN", + Kind::Blocking => "BLOCKING", + } + } + } } /// Task performance statistics. #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/console-api/src/generated/rs.tokio.console.trace.rs b/console-api/src/generated/rs.tokio.console.trace.rs index ed3e98991..13c2843a5 100644 --- a/console-api/src/generated/rs.tokio.console.trace.rs +++ b/console-api/src/generated/rs.tokio.console.trace.rs @@ -84,6 +84,7 @@ pub mod trace_event { pub mod trace_client { #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] use tonic::codegen::*; + use tonic::codegen::http::Uri; /// Allows observers to stream trace events for a given `WatchRequest` filter. #[derive(Debug, Clone)] pub struct TraceClient { @@ -104,19 +105,24 @@ pub mod trace_client { where T: tonic::client::GrpcService, T::Error: Into, - T::ResponseBody: Default + Body + Send + 'static, + T::ResponseBody: Body + Send + 'static, ::Error: Into + Send, { pub fn new(inner: T) -> Self { let inner = tonic::client::Grpc::new(inner); Self { inner } } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } pub fn with_interceptor( inner: T, interceptor: F, ) -> TraceClient> where F: tonic::service::Interceptor, + T::ResponseBody: Default, T: tonic::codegen::Service< http::Request, Response = http::Response< @@ -129,19 +135,19 @@ pub mod trace_client { { TraceClient::new(InterceptedService::new(inner, interceptor)) } - /// Compress requests with `gzip`. + /// Compress requests with the given encoding. /// /// This requires the server to support it otherwise it might respond with an /// error. #[must_use] - pub fn send_gzip(mut self) -> Self { - self.inner = self.inner.send_gzip(); + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); self } - /// Enable decompressing responses with `gzip`. + /// Enable decompressing responses. #[must_use] - pub fn accept_gzip(mut self) -> Self { - self.inner = self.inner.accept_gzip(); + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); self } /// Produces a stream of trace events for the given filter. @@ -192,8 +198,8 @@ pub mod trace_server { #[derive(Debug)] pub struct TraceServer { inner: _Inner, - accept_compression_encodings: (), - send_compression_encodings: (), + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, } struct _Inner(Arc); impl TraceServer { @@ -217,6 +223,18 @@ pub mod trace_server { { InterceptedService::new(Self::new(inner), interceptor) } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } } impl tonic::codegen::Service> for TraceServer where @@ -310,7 +328,7 @@ pub mod trace_server { write!(f, "{:?}", self.0) } } - impl tonic::transport::NamedService for TraceServer { + impl tonic::server::NamedService for TraceServer { const NAME: &'static str = "rs.tokio.console.trace.Trace"; } } diff --git a/console-api/tests/bootstrap.rs b/console-api/tests/bootstrap.rs index ed48c6a13..27c629cf4 100644 --- a/console-api/tests/bootstrap.rs +++ b/console-api/tests/bootstrap.rs @@ -1,38 +1,56 @@ -use std::{path::PathBuf, process::Command}; +use std::{fs, path::PathBuf, process::Command}; #[test] fn bootstrap() { - let iface_files = &[ - "proto/trace.proto", - "proto/common.proto", - "proto/tasks.proto", - "proto/instrument.proto", - "proto/resources.proto", - "proto/async_ops.proto", - ]; - let dirs = &["proto"]; + let root_dir = PathBuf::from(std::env!("CARGO_MANIFEST_DIR")); + let proto_dir = root_dir.join("proto"); + let proto_ext = std::ffi::OsStr::new("proto"); + let proto_files = fs::read_dir(&proto_dir).and_then(|dir| { + dir.filter_map(|entry| { + (|| { + let entry = entry?; + if entry.file_type()?.is_dir() { + return Ok(None); + } - let out_dir = PathBuf::from(std::env!("CARGO_MANIFEST_DIR")) - .join("src") - .join("generated"); + let path = entry.path(); + if path.extension() != Some(proto_ext) { + return Ok(None); + } - tonic_build::configure() + Ok(Some(path)) + })() + .transpose() + }) + .collect::, _>>() + }); + let proto_files = match proto_files { + Ok(files) => files, + Err(error) => panic!("failed to list proto files: {}", error), + }; + + let out_dir = root_dir.join("src").join("generated"); + + if let Err(error) = tonic_build::configure() .build_client(true) .build_server(true) + .emit_rerun_if_changed(false) .protoc_arg("--experimental_allow_proto3_optional") - .out_dir(format!("{}", out_dir.display())) - .compile(iface_files, dirs) - .unwrap(); + .out_dir(&out_dir) + .compile(&proto_files[..], &[proto_dir]) + { + panic!("failed to compile `console-api` protobuf: {}", error); + } let status = Command::new("git") .arg("diff") .arg("--exit-code") .arg("--") - .arg(format!("{}", out_dir.display())) - .status() - .unwrap(); - - if !status.success() { - panic!("You should commit the protobuf files"); + .arg(out_dir) + .status(); + match status { + Ok(status) if !status.success() => panic!("You should commit the protobuf files"), + Err(error) => panic!("failed to run `git diff`: {}", error), + Ok(_) => {} } } diff --git a/console-subscriber/Cargo.toml b/console-subscriber/Cargo.toml index aec867f43..82a68a5a6 100644 --- a/console-subscriber/Cargo.toml +++ b/console-subscriber/Cargo.toml @@ -35,8 +35,8 @@ crossbeam-utils = "0.8.7" tokio = { version = "^1.15", features = ["sync", "time", "macros", "tracing"] } tokio-stream = "0.1" thread_local = "1.1.3" -console-api = { version = "0.3.0", path = "../console-api", features = ["transport"] } -tonic = { version = "0.7", features = ["transport"] } +console-api = { version = "0.4.0", path = "../console-api", features = ["transport"] } +tonic = { version = "0.8", features = ["transport"] } tracing-core = "0.1.24" tracing = "0.1.26" tracing-subscriber = { version = "0.3.11", default-features = false, features = ["fmt", "registry"] } @@ -46,7 +46,7 @@ hdrhistogram = { version = "7.3.0", default-features = false, features = ["seria # feature to also enable `tracing-subscriber`'s parking_lot feature flag. parking_lot_crate = { package = "parking_lot", version = "0.11", optional = true } humantime = "2.1.0" -prost-types = "0.10.0" +prost-types = "0.11.0" # Required for recording: serde = { version = "1", features = ["derive"] } diff --git a/console-subscriber/src/stats.rs b/console-subscriber/src/stats.rs index 32fb364f1..24848307d 100644 --- a/console-subscriber/src/stats.rs +++ b/console-subscriber/src/stats.rs @@ -532,7 +532,13 @@ impl ToProto for PollStats { last_poll_ended: timestamps .last_poll_ended .map(|at| base_time.to_timestamp(at)), - busy_time: Some(timestamps.busy_time.into()), + busy_time: Some(timestamps.busy_time.try_into().unwrap_or_else(|error| { + eprintln!( + "failed to convert busy time to protobuf duration: {}", + error + ); + Default::default() + })), } } } diff --git a/tokio-console/Cargo.toml b/tokio-console/Cargo.toml index b5be2873e..ed332a7ea 100644 --- a/tokio-console/Cargo.toml +++ b/tokio-console/Cargo.toml @@ -27,16 +27,16 @@ keywords = [ [dependencies] atty = "0.2" -console-api = { version = "0.3.0", path = "../console-api", features = ["transport"] } +console-api = { version = "0.4.0", path = "../console-api", features = ["transport"] } clap = { version = "3", features = ["cargo", "derive", "env"] } tokio = { version = "1", features = ["full", "rt-multi-thread"] } -tonic = { version = "0.7", features = ["transport"] } +tonic = { version = "0.8", features = ["transport"] } futures = "0.3" tui = { version = "0.16.0", default-features = false, features = ["crossterm"] } tracing = "0.1" tracing-subscriber = { version = "0.3.0", features = ["env-filter"] } tracing-journald = { version = "0.2", optional = true } -prost-types = "0.10" +prost-types = "0.11" crossterm = { version = "0.20", features = ["event-stream"] } color-eyre = { version = "0.5", features = ["issue-url"] } hdrhistogram = { version = "7.3.0", default-features = false, features = ["serialization"] } diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 000000000..09f60941a --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "xtask" +version = "0.1.0" +license = "MIT" +edition = "2021" +rust-version = "1.56.0" +publish = false + +[dependencies] +tonic-build = { version = "0.8", default-features = false, features = [ + "prost", "transport" +] } +clap = { version = "3", features = ["derive"] } +color-eyre = "0.5" \ No newline at end of file diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 000000000..807dd2f1f --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,80 @@ +use clap::Parser; +use color_eyre::{ + eyre::{ensure, WrapErr}, + Result, +}; +use std::{fs, path::PathBuf}; + +/// tokio-console dev tasks +#[derive(Debug, clap::Parser)] +struct Args { + #[clap(subcommand)] + cmd: Command, +} + +#[derive(Debug, clap::Subcommand)] +enum Command { + /// Generate `console-api` protobuf bindings. + GenProto, +} + +fn main() -> Result<()> { + color_eyre::install()?; + Args::parse().cmd.run() +} + +impl Command { + fn run(&self) -> Result<()> { + match self { + Self::GenProto => gen_proto(), + } + } +} + +fn gen_proto() -> Result<()> { + eprintln!("generating `console-api` protos..."); + + let api_dir = { + let mut mydir = PathBuf::from(std::env!("CARGO_MANIFEST_DIR")); + ensure!(mydir.pop(), "manifest path should not be relative!"); + mydir.join("console-api") + }; + + let proto_dir = api_dir.join("proto"); + let proto_ext = std::ffi::OsStr::new("proto"); + let proto_files = fs::read_dir(&proto_dir) + .with_context(|| { + format!( + "failed to read protobuf directory `{}`", + proto_dir.display() + ) + })? + .filter_map(|entry| { + (|| { + let entry = entry?; + if entry.file_type()?.is_dir() { + return Ok(None); + } + + let path = entry.path(); + if path.extension() != Some(proto_ext) { + return Ok(None); + } + + Ok(Some(path)) + })() + .transpose() + }) + .collect::>>()?; + + let out_dir = api_dir.join("src").join("generated"); + + tonic_build::configure() + .build_client(true) + .build_server(true) + .emit_rerun_if_changed(false) + .protoc_arg("--experimental_allow_proto3_optional") + .out_dir(&out_dir) + .compile(&proto_files[..], &[proto_dir]) + .context("failed to compile protobuf files") +}