Skip to content

Commit

Permalink
Introduce exonum_build (exonum#1076)
Browse files Browse the repository at this point in the history
  • Loading branch information
dvush authored and aleksuss committed Dec 4, 2018
1 parent dd5f26b commit 42dc93d
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 74 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ The project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html)
- Now peers require only one connection to exchange messages between
them. (#945)

#### exonum_build

- `exonum_build` crate has been added to simplify writing `build.rs` files
for services that use protobuf code generation. (#1076)

### Bug Fixes

#### exonum
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ members = [
"examples/cryptocurrency-advanced/backend",
"examples/timestamping/backend",
"exonum_derive",
"exonum_build",
]
exclude = [ "exonum/fuzz" ]
3 changes: 1 addition & 2 deletions exonum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,4 @@ sodiumoxide-crypto = ["exonum_sodiumoxide"]
with-serde = []

[build-dependencies]
protoc-rust = "=2.2.0"
walkdir = "2.2.7"
exonum_build = { version = "0.9.0", path = "../exonum_build" }
4 changes: 1 addition & 3 deletions exonum/benches/criterion/proto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,4 @@ pub use self::bench_transactions::{CurrencyTx, TimestampTx};

include!(concat!(env!("OUT_DIR"), "/exonum_benches_proto_mod.rs"));

pub mod helpers {
pub use exonum::encoding::protobuf::helpers::*;
}
use exonum::encoding::protobuf::*;
72 changes: 6 additions & 66 deletions exonum/build.rs
Original file line number Diff line number Diff line change
@@ -1,70 +1,13 @@
// spell-checker:ignore rustc, walkdir
// spell-checker:ignore rustc

extern crate protoc_rust;
extern crate walkdir;
extern crate exonum_build;

use protoc_rust::Customize;
use walkdir::WalkDir;
use exonum_build::protobuf_generate;

use std::{env, fs::File, io::Write, path::Path, process::Command};

static USER_AGENT_FILE_NAME: &str = "user_agent";

fn get_proto_files<P: AsRef<Path>>(path: P) -> Vec<String> {
WalkDir::new(path)
.into_iter()
.filter_map(|e| {
let e = e.ok()?;
if e.path().extension()?.to_str() == Some("proto") {
Some(e.path().to_str()?.to_owned())
} else {
None
}
}).collect()
}

/// Workaround for https://github.com/stepancheg/rust-protobuf/issues/324
fn generate_mod_rs(out_dir: &str, proto_files: &[String], mod_file: &str) {
let mod_file_content = {
proto_files
.iter()
.map(|f| {
let mod_name = Path::new(f)
.file_stem()
.unwrap()
.to_str()
.expect("proto file name is not &str");
if mod_name == "tests" {
format!("#[cfg(test)]\npub mod {};\n", mod_name)
} else {
format!("pub mod {};\n", mod_name)
}
}).collect::<String>()
};
let dest_path = Path::new(&out_dir).join(mod_file);
let mut file = File::create(dest_path).expect("Unable to create output file");
file.write_all(mod_file_content.as_bytes())
.expect("Unable to write data to file");
}

fn protoc_generate(out_dir: &str, input_dir: &str, includes: &[&str], mod_file: &str) {
let proto_files = get_proto_files(input_dir);

generate_mod_rs(out_dir, &proto_files, mod_file);

protoc_rust::run(protoc_rust::Args {
out_dir,
input: &proto_files.iter().map(|s| s.as_ref()).collect::<Vec<_>>(),
includes,
customize: Customize {
serde_derive: Some(true),
..Default::default()
},
}).expect("protoc");

println!("cargo:rerun-if-changed={}", input_dir);
}

fn main() {
let package_name = option_env!("CARGO_PKG_NAME").unwrap_or("exonum");
let package_version = option_env!("CARGO_PKG_VERSION").unwrap_or("?");
Expand All @@ -77,16 +20,14 @@ fn main() {
file.write_all(user_agent.as_bytes())
.expect("Unable to write data to file");

protoc_generate(
&out_dir,
protobuf_generate(
"src/encoding/protobuf/proto/",
&["src/encoding/protobuf/proto"],
"exonum_proto_mod.rs",
);

// Exonum external tests.
protoc_generate(
&out_dir,
protobuf_generate(
"tests/explorer/blockchain/proto",
&[
"tests/explorer/blockchain/proto",
Expand All @@ -96,8 +37,7 @@ fn main() {
);

// Exonum benchmarks.
protoc_generate(
&out_dir,
protobuf_generate(
"benches/criterion/proto",
&["benches/criterion/proto", "src/encoding/protobuf/proto"],
"exonum_benches_proto_mod.rs",
Expand Down
4 changes: 1 addition & 3 deletions exonum/tests/explorer/blockchain/proto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,4 @@ pub use self::tests_transactions::{CreateWallet, Transfer};

include!(concat!(env!("OUT_DIR"), "/exonum_tests_proto_mod.rs"));

pub mod helpers {
pub use exonum::encoding::protobuf::helpers::*;
}
use exonum::encoding::protobuf::*;
12 changes: 12 additions & 0 deletions exonum_build/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "exonum_build"
version = "0.9.0"
authors = ["The Exonum Team <exonum@bitfury.com>"]
license = "Apache-2.0"
keywords = ["protobuf", "exonum"]
categories = ["development-tools"]
description = "Helper functions for writing build.rs for exonum services."

[dependencies]
protoc-rust = "=2.2.0"
walkdir = "2.2.7"
153 changes: 153 additions & 0 deletions exonum_build/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Copyright 2018 The Exonum Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// spell-checker:ignore walkdir, subfolders, submodules

//! This crate simplifies writing build.rs for exonum and exonum services.

extern crate protoc_rust;
extern crate walkdir;

use protoc_rust::Customize;
use walkdir::WalkDir;

use std::{
env,
fs::File,
io::Write,
path::{Path, PathBuf},
};

/// Finds all .proto files in `path` and subfolders and returns a vector of their paths.
fn get_proto_files<P: AsRef<Path>>(path: &P) -> Vec<PathBuf> {
WalkDir::new(path)
.into_iter()
.filter_map(|e| {
let e = e.ok()?;
if e.path().extension()?.to_str() == Some("proto") {
Some(e.path().into())
} else {
None
}
}).collect()
}

/// Workaround for https://github.com/stepancheg/rust-protobuf/issues/324
/// It is impossible to `include!` .rs files generated by rust-protobuf
/// so we generate piece of `mod.rs` which includes generated files as public submodules.
///
/// tests.proto .rs file will be included with `#[cfg(test)]`.
fn generate_mod_rs<P: AsRef<Path>, Q: AsRef<Path>>(
out_dir: &P,
proto_files: &[PathBuf],
mod_file: &Q,
) {
let mod_file_content = {
proto_files
.iter()
.map(|f| {
let mod_name = f
.file_stem()
.unwrap()
.to_str()
.expect(".proto file name is not convertible to &str");
if mod_name == "tests" {
format!("#[cfg(test)]\npub mod {};\n", mod_name)
} else {
format!("pub mod {};\n", mod_name)
}
}).collect::<String>()
};
let dest_path = out_dir.as_ref().join(mod_file);
let mut file = File::create(dest_path).expect("Unable to create output file");
file.write_all(mod_file_content.as_bytes())
.expect("Unable to write data to file");
}

/// Generates .rs files from .proto files.
///
/// `protoc` executable from protobuf should be in `$PATH`
///
/// # Examples
///
/// In `build.rs`
/// ```no_run
/// extern crate exonum_build;
///
/// use exonum_build::protobuf_generate;
///
/// // Includes usually should contain input_dir.
/// protobuf_generate("src/proto", &["src/proto"], "example_mod.rs")
/// ```
/// After successful run `$OUT_DIR` will contain \*.rs for each \*.proto file in
/// "src/proto/\*\*/" and example_mod.rs which will include all generated .rs files
/// as submodules.
///
/// To use generated protobuf structs.
///
/// In `src/proto/mod.rs`
/// ```ignore
/// extern crate exonum;
///
/// include!(concat!(env!("OUT_DIR"), "/example_mod.rs"));
///
/// // If you use types from exonum .proto files.
/// use exonum::encoding::protobuf::*;
/// ```
pub fn protobuf_generate<P, R, I, T>(input_dir: P, includes: I, mod_file_name: T)
where
P: AsRef<Path>,
R: AsRef<Path>,
I: IntoIterator<Item = R>,
T: AsRef<str>,
{
let out_dir = env::var("OUT_DIR")
.map(PathBuf::from)
.expect("Unable to get OUT_DIR");

let proto_files = get_proto_files(&input_dir);
generate_mod_rs(&out_dir, &proto_files, &mod_file_name.as_ref());

let includes = includes.into_iter().collect::<Vec<_>>();

protoc_rust::run(protoc_rust::Args {
out_dir: out_dir
.to_str()
.expect("Out dir name is not convertible to &str"),
input: &proto_files
.iter()
.map(|s| s.to_str().expect("File name is not convertible to &str"))
.collect::<Vec<_>>(),
includes: &includes
.iter()
.map(|s| {
s.as_ref()
.to_str()
.expect("Include dir name is not convertible to &str")
}).collect::<Vec<_>>(),
customize: Customize {
serde_derive: Some(true),
..Default::default()
},
}).expect("protoc");

// rerun build.rs if .proto files changed.
println!(
"cargo:rerun-if-changed={}",
input_dir
.as_ref()
.to_str()
.expect("Input dir name is not convertible to &str")
);
}

0 comments on commit 42dc93d

Please sign in to comment.