diff --git a/tvix/Cargo.lock b/tvix/Cargo.lock index a37c04f97..429bdfd25 100644 --- a/tvix/Cargo.lock +++ b/tvix/Cargo.lock @@ -3283,16 +3283,19 @@ name = "tvix-build" version = "0.1.0" dependencies = [ "bytes", + "clap", "itertools 0.12.0", "prost 0.12.1", "prost-build", "test-case", "thiserror", "tokio", + "tokio-listener", "tonic 0.10.2", "tonic-build", "tonic-reflection", "tracing", + "tracing-subscriber", "tvix-castore", "url", ] diff --git a/tvix/Cargo.nix b/tvix/Cargo.nix index d1ffbf326..e3f4be07e 100644 --- a/tvix/Cargo.nix +++ b/tvix/Cargo.nix @@ -10214,6 +10214,13 @@ rec { crateName = "tvix-build"; version = "0.1.0"; edition = "2021"; + crateBin = [ + { + name = "tvix-build"; + path = "src/bin/tvix-build.rs"; + requiredFeatures = [ ]; + } + ]; # We can't filter paths with references in Nix 2.4 # See https://github.com/NixOS/nix/issues/5410 src = @@ -10225,6 +10232,11 @@ rec { name = "bytes"; packageId = "bytes"; } + { + name = "clap"; + packageId = "clap"; + features = [ "derive" "env" ]; + } { name = "itertools"; packageId = "itertools 0.12.0"; @@ -10241,6 +10253,11 @@ rec { name = "tokio"; packageId = "tokio"; } + { + name = "tokio-listener"; + packageId = "tokio-listener"; + features = [ "tonic010" ]; + } { name = "tonic"; packageId = "tonic 0.10.2"; @@ -10255,6 +10272,11 @@ rec { name = "tracing"; packageId = "tracing"; } + { + name = "tracing-subscriber"; + packageId = "tracing-subscriber"; + features = [ "json" ]; + } { name = "tvix-castore"; packageId = "tvix-castore"; diff --git a/tvix/build/Cargo.toml b/tvix/build/Cargo.toml index 08102a66b..289dda4d8 100644 --- a/tvix/build/Cargo.toml +++ b/tvix/build/Cargo.toml @@ -5,13 +5,16 @@ edition = "2021" [dependencies] bytes = "1.4.0" +clap = { version = "4.0", features = ["derive", "env"] } itertools = "0.12.0" prost = "0.12.1" thiserror = "1.0.56" tokio = { version = "1.32.0" } +tokio-listener = { version = "0.2.2", features = [ "tonic010" ] } tonic = { version = "0.10.2", features = ["tls", "tls-roots"] } tvix-castore = { path = "../castore" } tracing = "0.1.37" +tracing-subscriber = { version = "0.3.16", features = ["json"] } url = "2.4.0" [dependencies.tonic-reflection] diff --git a/tvix/build/src/bin/tvix-build.rs b/tvix/build/src/bin/tvix-build.rs new file mode 100644 index 000000000..ed36c8933 --- /dev/null +++ b/tvix/build/src/bin/tvix-build.rs @@ -0,0 +1,132 @@ +use std::sync::Arc; + +use clap::Parser; +use clap::Subcommand; +use tokio_listener::Listener; +use tokio_listener::SystemOptions; +use tokio_listener::UserOptions; +use tonic::{self, transport::Server}; +use tracing::{info, Level}; +use tracing_subscriber::prelude::*; +use tvix_build::{ + buildservice, + proto::{build_service_server::BuildServiceServer, GRPCBuildServiceWrapper}, +}; +use tvix_castore::blobservice; +use tvix_castore::directoryservice; + +#[cfg(feature = "tonic-reflection")] +use tvix_build::proto::FILE_DESCRIPTOR_SET; +#[cfg(feature = "tonic-reflection")] +use tvix_castore::proto::FILE_DESCRIPTOR_SET as CASTORE_FILE_DESCRIPTOR_SET; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Cli { + /// Whether to log in JSON + #[arg(long)] + json: bool, + + #[arg(long)] + log_level: Option, + + #[command(subcommand)] + command: Commands, +} +#[derive(Subcommand)] +enum Commands { + /// Runs the tvix-build daemon. + Daemon { + #[arg(long, short = 'l')] + listen_address: Option, + + #[arg(long, env, default_value = "grpc+http://[::1]:8000")] + blob_service_addr: String, + + #[arg(long, env, default_value = "grpc+http://[::1]:8000")] + directory_service_addr: String, + + #[arg(long, env, default_value = "dummy://")] + build_service_addr: String, + }, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let cli = Cli::parse(); + + // configure log settings + let level = cli.log_level.unwrap_or(Level::INFO); + + let subscriber = tracing_subscriber::registry() + .with( + cli.json.then_some( + tracing_subscriber::fmt::Layer::new() + .with_writer(std::io::stderr.with_max_level(level)) + .json(), + ), + ) + .with( + (!cli.json).then_some( + tracing_subscriber::fmt::Layer::new() + .with_writer(std::io::stderr.with_max_level(level)) + .pretty(), + ), + ); + + tracing::subscriber::set_global_default(subscriber).expect("Unable to set global subscriber"); + + match cli.command { + Commands::Daemon { + listen_address, + blob_service_addr, + directory_service_addr, + build_service_addr, + } => { + // initialize stores + let blob_service = blobservice::from_addr(&blob_service_addr).await?; + let directory_service = directoryservice::from_addr(&directory_service_addr).await?; + + let build_service = buildservice::from_addr( + &build_service_addr, + Arc::from(blob_service), + Arc::from(directory_service), + ) + .await?; + + let listen_address = listen_address + .unwrap_or_else(|| "[::]:8000".to_string()) + .parse() + .unwrap(); + + let mut server = Server::builder(); + + #[allow(unused_mut)] + let mut router = server.add_service(BuildServiceServer::new( + GRPCBuildServiceWrapper::new(build_service), + )); + + #[cfg(feature = "tonic-reflection")] + { + let reflection_svc = tonic_reflection::server::Builder::configure() + .register_encoded_file_descriptor_set(CASTORE_FILE_DESCRIPTOR_SET) + .register_encoded_file_descriptor_set(FILE_DESCRIPTOR_SET) + .build()?; + router = router.add_service(reflection_svc); + } + + info!(listen_address=%listen_address, "listening"); + + let listener = Listener::bind( + &listen_address, + &SystemOptions::default(), + &UserOptions::default(), + ) + .await?; + + router.serve_with_incoming(listener).await?; + } + } + + Ok(()) +}