use prost_types::field_descriptor_proto::Type;
use std::collections::BTreeSet;
use std::env;
use std::fmt::Write;
use std::fs;
use std::path::PathBuf;
use std::process::Command;
fn main() {
let protoc = protoc_bin_vendored::protoc_bin_path().expect("vendored protoc");
unsafe { env::set_var("PROTOC", &protoc) };
println!("cargo::rerun-if-changed=proto/wire.proto");
let mut config = prost_build::Config::new();
let descriptors = config
.load_fds(&["proto/wire.proto"], &["proto/"])
.expect("failed to load wire.proto");
let mut messages = BTreeSet::new();
let mut conversions = String::new();
let mut envelope_names = String::new();
for message in descriptors.file.iter().flat_map(|file| &file.message_type) {
if !matches!(message.name(), "HostToArk" | "ArkToHost") {
continue;
}
let oneof = message
.oneof_decl
.iter()
.position(|oneof| oneof.name() == "content")
.expect("envelope content oneof");
let module = match message.name() {
"HostToArk" => "host_to_ark",
"ArkToHost" => "ark_to_host",
_ => unreachable!(),
};
writeln!(conversions, "contents! {{ {module},").unwrap();
writeln!(
envelope_names,
"impl opaque::{module}::Content {{\n fn name(&self) -> &'static str {{\n match self {{"
)
.unwrap();
for field in &message.field {
if field.oneof_index != Some(oneof as i32) {
continue;
}
let (variant, payload) = match field.type_name.as_deref() {
Some(name) => {
let name = name.rsplit('.').next().expect("protobuf message name");
(name, name)
}
None => {
assert_eq!(field.r#type().as_str_name(), "TYPE_BYTES");
assert_eq!(field.name(), "develop");
("Develop", "Vec<u8>")
}
};
messages.insert((variant, payload, field.name()));
let field_variant: String = field
.name()
.split('_')
.map(|word| {
let mut chars = word.chars();
chars
.next()
.expect("nonempty schema identifier")
.to_uppercase()
.collect::<String>()
+ chars.as_str()
})
.collect();
writeln!(conversions, " {field_variant} => {variant},").unwrap();
writeln!(
envelope_names,
" Self::{field_variant}(..) => {:?},",
field.name()
)
.unwrap();
}
writeln!(conversions, "}}").unwrap();
writeln!(envelope_names, " }}\n }}\n}}").unwrap();
}
let mut content = String::from("messages! {\n");
for (variant, payload, _) in &messages {
writeln!(content, " {variant}({payload}),").unwrap();
}
writeln!(content, "}}").unwrap();
content.push_str(&conversions);
writeln!(
content,
"impl Message {{\n /// Returns the payload's protobuf field name for log lines.\n pub(super) fn field_name(&self) -> &'static str {{\n match self {{"
)
.unwrap();
for (variant, _, name) in &messages {
writeln!(content, " Self::{variant}(..) => {name:?},").unwrap();
}
writeln!(content, " }}\n }}\n}}").unwrap();
let out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("build output directory"));
fs::write(out_dir.join("message.rs"), content).expect("write message enum and conversions");
fs::write(out_dir.join("darkbio.wire.names.rs"), envelope_names)
.expect("write envelope field names");
let mut opaque = descriptors.clone();
for file in &mut opaque.file {
file.package = Some("darkbio.wire.opaque".into());
file.enum_type.clear();
file.message_type
.retain(|message| matches!(message.name(), "HostToArk" | "ArkToHost"));
for message in &mut file.message_type {
for field in &mut message.field {
if field.r#type() == Type::Message {
field.r#type = Some(Type::Bytes as i32);
field.type_name = None;
}
}
}
}
let mut opaque_config = prost_build::Config::new();
opaque_config.bytes(["."]);
opaque_config
.compile_fds(opaque)
.expect("generate opaque envelope views");
config
.compile_fds(descriptors)
.expect("failed to compile wire.proto");
let output = Command::new("rustc")
.arg("--version")
.output()
.expect("Failed to execute rustc");
let version = String::from_utf8(output.stdout)
.expect("Invalid UTF-8 from rustc")
.trim()
.to_string();
println!("cargo::rerun-if-env-changed=RUSTC");
println!("cargo::rustc-env=RUSTC_VERSION={}", version);
}