diff --git a/Cargo.lock b/Cargo.lock index 4f3c4654da..2c6344ec0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -478,6 +478,14 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "astria-build-info" +version = "0.1.0" +dependencies = [ + "serde", + "vergen", +] + [[package]] name = "astria-celestia-client" version = "0.1.0" @@ -536,6 +544,7 @@ dependencies = [ name = "astria-composer" version = "0.3.1" dependencies = [ + "astria-build-info", "astria-config", "astria-core", "astria-sequencer-client", @@ -576,6 +585,7 @@ dependencies = [ name = "astria-conductor" version = "0.11.1" dependencies = [ + "astria-build-info", "astria-celestia-client", "astria-config", "astria-core", @@ -679,6 +689,7 @@ name = "astria-sequencer" version = "0.8.0" dependencies = [ "anyhow", + "astria-build-info", "astria-config", "astria-core", "astria-merkle", @@ -741,6 +752,7 @@ dependencies = [ name = "astria-sequencer-relayer" version = "0.9.1" dependencies = [ + "astria-build-info", "astria-celestia-client", "astria-celestia-mock", "astria-config", @@ -4758,6 +4770,15 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "object" version = "0.32.2" @@ -7413,7 +7434,9 @@ checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ "deranged", "itoa", + "libc", "num-conv", + "num_threads", "powerfmt", "serde", "time-core", @@ -8185,6 +8208,22 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "vergen" +version = "8.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27d6bdd219887a9eadd19e1c34f32e47fa332301184935c6d9bca26f3cca525" +dependencies = [ + "anyhow", + "cargo_metadata", + "cfg-if", + "git2", + "regex", + "rustc_version 0.4.0", + "rustversion", + "time", +] + [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index 3803908895..1e79a1bd2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ exclude = ["tools/protobuf-compiler"] members = [ + "crates/astria-build-info", "crates/astria-celestia-client", "crates/astria-celestia-mock", "crates/astria-cli", @@ -22,6 +23,7 @@ members = [ # Specify default members so that cargo invocations in github actions will # not act on lints default-members = [ + "crates/astria-build-info", "crates/astria-celestia-client", "crates/astria-celestia-mock", "crates/astria-cli", @@ -60,7 +62,7 @@ hex = "0.4" hex-literal = "0.4.1" humantime = "2.1.0" hyper = "0.14" -ibc-types = "0.11.0" +ibc-types = "0.11.1" jsonrpsee = { version = "0.20" } once_cell = "1.17.1" sha2 = "0.10" diff --git a/crates/astria-build-info/Cargo.toml b/crates/astria-build-info/Cargo.toml new file mode 100644 index 0000000000..b0083bbc69 --- /dev/null +++ b/crates/astria-build-info/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "astria-build-info" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies.serde] +workspace = true +features = ["derive"] +optional = true + +[dependencies.vergen] +version = "8" +features = ["build", "cargo", "rustc", "git", "git2"] +optional = true + +[features] +build = ["dep:vergen"] +runtime = ["dep:serde"] diff --git a/crates/astria-build-info/README.md b/crates/astria-build-info/README.md new file mode 100644 index 0000000000..9dacdfec08 --- /dev/null +++ b/crates/astria-build-info/README.md @@ -0,0 +1,51 @@ +# `astria-build-info` + +Inject build information about services and binaries at compile time. + +`astria-build-info` provides the `astria_build_info::emit` utility +and the `astria_build_info::get!` macro to inject build information +into binaries. + +`emit` is used to emit environment variables in a binary's build script, +while `get!` picks up the environment variables and constructs a `BuildInfo` +at compile time. `get!` is a macro so that it runs in the compilation context +of the binary and not in the context of the source package. + +## Features + +`astria-build-info` provides two features (both disabled by default): + ++ `build` to enable the `emit` utility. ++ `runtime` to enable `BuildInfo` and the `get!` macro. + +## Usage + +Set up a service's dependencies like so: + +```toml +[dependencies] +astria-build-info = { path = "../astria-build-info", features = ["runtime"] } + +[build-dependencies] +astria-build-info = { path = "../astria-build-info", features = ["build"] } +``` + +And then use `emit` in the binary's build.rs, specifying the git tag which is +used for the service. For example, if the service is tagged with +`shaving-cats-v0.1.2`, then provide `emit("shaving-cats-v")` (supplying +`"shaving-cats"` also works, but it is recommened to use `"shaving-cats-v"` in +case there is another service/tag named `"shaving-cats-and-dogs-v1.2.3"`). + +```rust,ignore +fn main() -> Result<(), Box> { + astria_build_info::emit("")?; + Ok(()) +} +``` + +And pick up the emitted variables like so: + +```rust,ignore +use astria_build_info::BuildInfo; +const BUILD_INFO: BuildInfo = astria_build_info::get!(); +``` diff --git a/crates/astria-build-info/src/lib.rs b/crates/astria-build-info/src/lib.rs new file mode 100644 index 0000000000..92940581d6 --- /dev/null +++ b/crates/astria-build-info/src/lib.rs @@ -0,0 +1,97 @@ +#![doc = include_str!("../README.md")] +#![cfg_attr(docsrs, feature(doc_cfg))] + +#[cfg(feature = "runtime")] +pub use runtime::BuildInfo; + +#[cfg(feature = "runtime")] +#[cfg_attr(docsrs, doc(cfg(feature = "runtime")))] +mod runtime { + /// Constructs a [`BuildInfo`] at compile time. + #[macro_export] + macro_rules! get { + () => { + BuildInfo { + build_timestamp: env!("VERGEN_BUILD_TIMESTAMP"), + cargo_opt_level: env!("VERGEN_CARGO_OPT_LEVEL"), + cargo_pkg_name: env!("CARGO_PKG_NAME"), + cargo_target_triple: env!("VERGEN_CARGO_TARGET_TRIPLE"), + git_branch: env!("VERGEN_GIT_BRANCH"), + git_commit_date: env!("VERGEN_GIT_COMMIT_DATE"), + git_describe: env!("VERGEN_GIT_DESCRIBE"), + git_sha: env!("VERGEN_GIT_SHA"), + rustc_channel: env!("VERGEN_RUSTC_CHANNEL"), + rustc_commit_hash: env!("VERGEN_RUSTC_COMMIT_HASH"), + rustc_host_triple: env!("VERGEN_RUSTC_HOST_TRIPLE"), + } + }; + } + + /// The build info of a package constructed at compile time. + /// + /// This intended to be constructed at compile time using the + /// [`get`] macro: + /// + /// ```ignore + /// # use astria_build_info::BuildInfo; + /// const BUILD_INFO: BuildInfo = astria_build_info::get!(); + /// ``` + #[derive(Debug, serde::Serialize)] + pub struct BuildInfo { + pub build_timestamp: &'static str, + pub cargo_opt_level: &'static str, + pub cargo_pkg_name: &'static str, + pub cargo_target_triple: &'static str, + pub git_branch: &'static str, + pub git_commit_date: &'static str, + pub git_describe: &'static str, + pub git_sha: &'static str, + pub rustc_channel: &'static str, + pub rustc_commit_hash: &'static str, + pub rustc_host_triple: &'static str, + } +} + +#[cfg(feature = "build")] +#[cfg_attr(docsrs, doc(cfg(feature = "build")))] +/// Emits build infos as environment variables. +/// +/// The `prefix` argument is used akin to manually calling +/// `git describe --tags --match="*"`. It assumes that releases of +/// services (and binaries) are tagged using a format like `0.1.2`. +/// +/// Note that if two services share a common prefix like `sequencer` and +/// `sequencer-relayer`, `` should be supplied as `"sequencer-v"` and +/// `"sequencer-relayer-v"` for tags like `sequencer-v1.2.3` or +/// `sequencer-relayer-v1.2.3`. This is to avoid matching on the wrong prefix +/// +/// # Errors +/// Emits the same errors as [`vergen::EmitBuilder::emit`]. +/// +/// # Usage +/// +/// In a crate's `build.rs` write: +/// +/// ```no_run +/// # fn main() -> Result<(), Box> { +/// astria_build_info::emit("crate-release-tag")?; +/// # Ok(()) +/// # } +/// ``` +pub fn emit(prefix: &str) -> Result<(), Box> { + let git_describe_prefix = Box::leak(format!("{prefix}*").into_boxed_str()); + vergen::EmitBuilder::builder() + .build_timestamp() + .cargo_opt_level() + .cargo_target_triple() + .git_branch() + .git_commit_date() + .git_describe(true, true, Some(&*git_describe_prefix)) + .git_sha(false) + .rustc_channel() + .rustc_commit_hash() + .rustc_host_triple() + .rustc_semver() + .emit()?; + Ok(()) +} diff --git a/crates/astria-composer/Cargo.toml b/crates/astria-composer/Cargo.toml index 4555ba4512..b37d563d39 100644 --- a/crates/astria-composer/Cargo.toml +++ b/crates/astria-composer/Cargo.toml @@ -12,6 +12,7 @@ homepage = "https://astria.org" name = "astria-composer" [dependencies] +astria-build-info = { path = "../astria-build-info", features = ["runtime"] } astria-core = { path = "../astria-core" } config = { package = "astria-config", path = "../astria-config" } telemetry = { package = "astria-telemetry", path = "../astria-telemetry" } @@ -65,3 +66,6 @@ tokio-test = { workspace = true } tendermint-rpc = { workspace = true } wiremock = { workspace = true } + +[build-dependencies] +astria-build-info = { path = "../astria-build-info", features = ["build"] } diff --git a/crates/astria-composer/build.rs b/crates/astria-composer/build.rs new file mode 100644 index 0000000000..cc691c2985 --- /dev/null +++ b/crates/astria-composer/build.rs @@ -0,0 +1,4 @@ +pub fn main() -> Result<(), Box> { + astria_build_info::emit("composer-v")?; + Ok(()) +} diff --git a/crates/astria-composer/src/build_info.rs b/crates/astria-composer/src/build_info.rs new file mode 100644 index 0000000000..2996fcab96 --- /dev/null +++ b/crates/astria-composer/src/build_info.rs @@ -0,0 +1,3 @@ +use astria_build_info::BuildInfo; + +pub const BUILD_INFO: BuildInfo = astria_build_info::get!(); diff --git a/crates/astria-composer/src/lib.rs b/crates/astria-composer/src/lib.rs index edd33726e9..cd12369cce 100644 --- a/crates/astria-composer/src/lib.rs +++ b/crates/astria-composer/src/lib.rs @@ -38,10 +38,12 @@ //! ``` pub(crate) mod api; +mod build_info; mod composer; pub mod config; pub(crate) mod searcher; +pub use build_info::BUILD_INFO; pub use composer::Composer; pub use config::Config; pub use telemetry; diff --git a/crates/astria-composer/src/main.rs b/crates/astria-composer/src/main.rs index 9dbdf05e2a..96c1a21e5f 100644 --- a/crates/astria-composer/src/main.rs +++ b/crates/astria-composer/src/main.rs @@ -4,12 +4,18 @@ use astria_composer::{ telemetry, Composer, Config, + BUILD_INFO, }; use color_eyre::eyre::WrapErr as _; use tracing::info; #[tokio::main] async fn main() -> ExitCode { + eprintln!( + "{}", + serde_json::to_string(&BUILD_INFO) + .expect("build info is serializable because it contains only unicode fields") + ); let cfg: Config = match config::get() { Ok(cfg) => cfg, Err(e) => { diff --git a/crates/astria-conductor/Cargo.toml b/crates/astria-conductor/Cargo.toml index 8ccc2b8694..9baf793c09 100644 --- a/crates/astria-conductor/Cargo.toml +++ b/crates/astria-conductor/Cargo.toml @@ -43,6 +43,7 @@ tonic = { workspace = true } tracing = { workspace = true, features = ["valuable"] } tryhard = { workspace = true } +astria-build-info = { path = "../astria-build-info", features = ["runtime"] } astria-core = { path = "../astria-core", features = ["client", "serde"] } celestia-client = { package = "astria-celestia-client", path = "../astria-celestia-client" } optimism = { package = "astria-optimism", path = "../astria-optimism" } @@ -67,3 +68,6 @@ optimism = { package = "astria-optimism", path = "../astria-optimism", features config = { package = "astria-config", path = "../astria-config", features = [ "tests", ] } + +[build-dependencies] +astria-build-info = { path = "../astria-build-info", features = ["build"] } diff --git a/crates/astria-conductor/build.rs b/crates/astria-conductor/build.rs new file mode 100644 index 0000000000..e6ea6d0c73 --- /dev/null +++ b/crates/astria-conductor/build.rs @@ -0,0 +1,4 @@ +fn main() -> Result<(), Box> { + astria_build_info::emit("conductor-v")?; + Ok(()) +} diff --git a/crates/astria-conductor/src/build_info.rs b/crates/astria-conductor/src/build_info.rs new file mode 100644 index 0000000000..2996fcab96 --- /dev/null +++ b/crates/astria-conductor/src/build_info.rs @@ -0,0 +1,3 @@ +use astria_build_info::BuildInfo; + +pub const BUILD_INFO: BuildInfo = astria_build_info::get!(); diff --git a/crates/astria-conductor/src/lib.rs b/crates/astria-conductor/src/lib.rs index 217acfc90a..5bc0e8c3b0 100644 --- a/crates/astria-conductor/src/lib.rs +++ b/crates/astria-conductor/src/lib.rs @@ -9,6 +9,7 @@ //! execution layer until it's received from the data availability layer. In the second case, the //! execution layer is notified to mark the block as finalized. pub(crate) mod block_cache; +mod build_info; pub(crate) mod celestia; pub(crate) mod client_provider; pub mod conductor; @@ -19,6 +20,7 @@ pub(crate) mod utils; use std::fmt::Write; +pub use build_info::BUILD_INFO; pub use conductor::Conductor; pub use config::Config; diff --git a/crates/astria-conductor/src/main.rs b/crates/astria-conductor/src/main.rs index b0d90f945f..4877b0bdbd 100644 --- a/crates/astria-conductor/src/main.rs +++ b/crates/astria-conductor/src/main.rs @@ -4,6 +4,7 @@ use astria_conductor::{ install_error_handler, Conductor, Config, + BUILD_INFO, }; use eyre::WrapErr as _; use tracing::{ @@ -17,6 +18,12 @@ const EX_CONFIG: u8 = 78; #[tokio::main] async fn main() -> ExitCode { + eprintln!( + "{}", + serde_json::to_string(&BUILD_INFO) + .expect("build info is serializable because it contains only unicode fields") + ); + install_error_handler().expect("must be able to install error formatter"); let cfg: Config = match config::get().wrap_err("failed reading config") { diff --git a/crates/astria-sequencer-relayer/Cargo.toml b/crates/astria-sequencer-relayer/Cargo.toml index ff43d52e0d..2628a8899b 100644 --- a/crates/astria-sequencer-relayer/Cargo.toml +++ b/crates/astria-sequencer-relayer/Cargo.toml @@ -39,6 +39,7 @@ tendermint-rpc = { workspace = true, features = ["http-client"] } tracing = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +astria-build-info = { path = "../astria-build-info", features = ["runtime"] } astria-core = { path = "../astria-core" } celestia-client = { package = "astria-celestia-client", path = "../astria-celestia-client" } config = { package = "astria-config", path = "../astria-config" } @@ -61,3 +62,6 @@ once_cell = { workspace = true } tempfile = { workspace = true } tokio = { workspace = true, features = ["test-util"] } wiremock = { workspace = true } + +[build-dependencies] +astria-build-info = { path = "../astria-build-info", features = ["build"] } diff --git a/crates/astria-sequencer-relayer/build.rs b/crates/astria-sequencer-relayer/build.rs new file mode 100644 index 0000000000..bdebbde4dc --- /dev/null +++ b/crates/astria-sequencer-relayer/build.rs @@ -0,0 +1,4 @@ +pub fn main() -> Result<(), Box> { + astria_build_info::emit("sequencer-relayer-v")?; + Ok(()) +} diff --git a/crates/astria-sequencer-relayer/src/build_info.rs b/crates/astria-sequencer-relayer/src/build_info.rs new file mode 100644 index 0000000000..2996fcab96 --- /dev/null +++ b/crates/astria-sequencer-relayer/src/build_info.rs @@ -0,0 +1,3 @@ +use astria_build_info::BuildInfo; + +pub const BUILD_INFO: BuildInfo = astria_build_info::get!(); diff --git a/crates/astria-sequencer-relayer/src/lib.rs b/crates/astria-sequencer-relayer/src/lib.rs index 9eb1747905..ef91ce1183 100644 --- a/crates/astria-sequencer-relayer/src/lib.rs +++ b/crates/astria-sequencer-relayer/src/lib.rs @@ -1,10 +1,12 @@ pub(crate) mod api; +mod build_info; pub mod config; pub mod metrics_init; pub(crate) mod relayer; pub mod sequencer_relayer; pub(crate) mod validator; +pub use build_info::BUILD_INFO; pub use config::Config; pub use sequencer_relayer::SequencerRelayer; pub use telemetry; diff --git a/crates/astria-sequencer-relayer/src/main.rs b/crates/astria-sequencer-relayer/src/main.rs index 9ba2e77e09..6ff14b05c3 100644 --- a/crates/astria-sequencer-relayer/src/main.rs +++ b/crates/astria-sequencer-relayer/src/main.rs @@ -5,12 +5,18 @@ use astria_sequencer_relayer::{ telemetry, Config, SequencerRelayer, + BUILD_INFO, }; use eyre::WrapErr as _; use tracing::info; #[tokio::main] async fn main() -> ExitCode { + eprintln!( + "{}", + serde_json::to_string(&BUILD_INFO) + .expect("build info is serializable because it contains only unicode fields") + ); let cfg: Config = config::get().expect("failed to read configuration"); let mut telemetry_conf = telemetry::configure() diff --git a/crates/astria-sequencer/Cargo.toml b/crates/astria-sequencer/Cargo.toml index 9e8eee9d6d..4828f4e39a 100644 --- a/crates/astria-sequencer/Cargo.toml +++ b/crates/astria-sequencer/Cargo.toml @@ -11,8 +11,13 @@ homepage = "https://astria.org" [[bin]] name = "astria-sequencer" +[features] +default = [] +mint = [] + [dependencies] -"astria-core" = { path = "../astria-core" } +astria-core = { path = "../astria-core" } +astria-build-info = { path = "../astria-build-info", features = ["runtime"] } config = { package = "astria-config", path = "../astria-config" } merkle = { package = "astria-merkle", path = "../astria-merkle" } telemetry = { package = "astria-telemetry", path = "../astria-telemetry", features = [ @@ -56,6 +61,5 @@ config = { package = "astria-config", path = "../astria-config", features = [ "tests", ] } -[features] -default = [] -mint = [] +[build-dependencies] +astria-build-info = { path = "../astria-build-info", features = ["build"] } diff --git a/crates/astria-sequencer/build.rs b/crates/astria-sequencer/build.rs new file mode 100644 index 0000000000..1f96f9d572 --- /dev/null +++ b/crates/astria-sequencer/build.rs @@ -0,0 +1,4 @@ +pub fn main() -> Result<(), Box> { + astria_build_info::emit("sequencer-v")?; + Ok(()) +} diff --git a/crates/astria-sequencer/src/build_info.rs b/crates/astria-sequencer/src/build_info.rs new file mode 100644 index 0000000000..2996fcab96 --- /dev/null +++ b/crates/astria-sequencer/src/build_info.rs @@ -0,0 +1,3 @@ +use astria_build_info::BuildInfo; + +pub const BUILD_INFO: BuildInfo = astria_build_info::get!(); diff --git a/crates/astria-sequencer/src/lib.rs b/crates/astria-sequencer/src/lib.rs index a84917ccb0..eb300ce401 100644 --- a/crates/astria-sequencer/src/lib.rs +++ b/crates/astria-sequencer/src/lib.rs @@ -2,6 +2,7 @@ pub(crate) mod accounts; pub(crate) mod app; pub(crate) mod asset; pub(crate) mod authority; +mod build_info; pub(crate) mod component; pub mod config; pub(crate) mod fee_asset_change; @@ -16,6 +17,7 @@ pub(crate) mod service; pub(crate) mod state_ext; pub(crate) mod transaction; +pub use build_info::BUILD_INFO; pub use config::Config; pub use sequencer::Sequencer; pub use telemetry; diff --git a/crates/astria-sequencer/src/main.rs b/crates/astria-sequencer/src/main.rs index b0be057f2e..c6dc37423f 100644 --- a/crates/astria-sequencer/src/main.rs +++ b/crates/astria-sequencer/src/main.rs @@ -4,6 +4,7 @@ use anyhow::Context as _; use astria_sequencer::{ Config, Sequencer, + BUILD_INFO, }; use tracing::info; @@ -13,6 +14,12 @@ const EX_CONFIG: u8 = 78; #[tokio::main] async fn main() -> ExitCode { + eprintln!( + "{}", + serde_json::to_string(&BUILD_INFO) + .expect("build info is serializable because it contains only unicode fields") + ); + let cfg: Config = match config::get() { Ok(cfg) => cfg, Err(e) => {