Skip to content

Commit

Permalink
Make prost a no_std compatible library, and prost-build able to gen…
Browse files Browse the repository at this point in the history
…erate `no_std` code

The alternative is to get collections types from `core` and `alloc`.
In the `no_std` mode in `prost_build`, we force it to always use BTreeMap
since HashMap was not stabilized in `alloc::collections` library.

The functionality is identical and the only incompatibilities in the interface
are that we cannot use `std::error::Error` or `std::io::Error` in `no_std` mode
because these types have not been moved to `alloc` and there is no alternative.
  • Loading branch information
cbeck88 committed Aug 13, 2019
1 parent 9551f28 commit e9f2c51
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 21 deletions.
6 changes: 5 additions & 1 deletion Cargo.toml
Expand Up @@ -25,6 +25,7 @@ members = [
"protobuf",
"tests",
"tests-2015",
"tests-alloc",
]
exclude = [
# The fuzz crate can't be compiled or tested without the 'cargo fuzz' command,
Expand All @@ -33,8 +34,11 @@ exclude = [
]

[features]
default = ["prost-derive"]
default = ["prost-derive", "std"]
no-recursion-limit = []
std = [] # When disabled, we attempt to provide no_std support in prost
# Config::use_alloc_collections() should be set when using prost_build in your build.rs
# so that generated files will not have std:: either, and will use alloc crate instead

[dependencies]
bytes = "0.4.7"
Expand Down
20 changes: 12 additions & 8 deletions prost-build/src/code_generator.rs
Expand Up @@ -18,6 +18,7 @@ use crate::extern_paths::ExternPaths;
use crate::ident::{match_ident, to_snake, to_upper_camel};
use crate::message_graph::MessageGraph;
use crate::Config;
use crate::CollectionsLib;

#[derive(PartialEq)]
enum Syntax {
Expand Down Expand Up @@ -366,12 +367,14 @@ impl<'a> CodeGenerator<'a> {
self.buf.push_str(&to_snake(field.name()));
self.buf.push_str(": ");
if repeated {
self.buf.push_str("::std::vec::Vec<");
self.buf.push_str(self.config.collections_lib.to_str());
self.buf.push_str("::vec::Vec<");
} else if optional {
self.buf.push_str("::std::option::Option<");
self.buf.push_str("::core::option::Option<");
}
if boxed {
self.buf.push_str("::std::boxed::Box<");
self.buf.push_str(self.config.collections_lib.to_str());
self.buf.push_str("::boxed::Box<");
}
self.buf.push_str(&ty);
if boxed {
Expand Down Expand Up @@ -403,7 +406,7 @@ impl<'a> CodeGenerator<'a> {
self.append_doc();
self.push_indent();

let btree_map = self
let btree_map = (self.config.collections_lib != CollectionsLib::Std) || self
.config
.btree_map
.iter()
Expand All @@ -426,8 +429,9 @@ impl<'a> CodeGenerator<'a> {
self.append_field_attributes(msg_name, field.name());
self.push_indent();
self.buf.push_str(&format!(
"pub {}: ::std::collections::{}<{}, {}>,\n",
"pub {}: {}::collections::{}<{}, {}>,\n",
to_snake(field.name()),
self.config.collections_lib.to_str(),
rust_ty,
key_ty,
value_ty
Expand Down Expand Up @@ -459,7 +463,7 @@ impl<'a> CodeGenerator<'a> {
self.append_field_attributes(fq_message_name, oneof.name());
self.push_indent();
self.buf.push_str(&format!(
"pub {}: ::std::option::Option<{}>,\n",
"pub {}: ::core::option::Option<{}>,\n",
to_snake(oneof.name()),
name
));
Expand Down Expand Up @@ -713,8 +717,8 @@ impl<'a> CodeGenerator<'a> {
Type::Int32 | Type::Sfixed32 | Type::Sint32 | Type::Enum => String::from("i32"),
Type::Int64 | Type::Sfixed64 | Type::Sint64 => String::from("i64"),
Type::Bool => String::from("bool"),
Type::String => String::from("std::string::String"),
Type::Bytes => String::from("std::vec::Vec<u8>"),
Type::String => [self.config.collections_lib.to_str(), "::string::String"].concat(),
Type::Bytes => [self.config.collections_lib.to_str(), "::vec::Vec<u8>"].concat(),
Type::Group | Type::Message => self.resolve_ident(field.type_name()),
}
}
Expand Down
31 changes: 31 additions & 0 deletions prost-build/src/lib.rs
Expand Up @@ -165,11 +165,32 @@ pub trait ServiceGenerator {
fn finalize(&mut self, _buf: &mut String) {}
}

/// Configuration enum for whether to use `std` prefixes or `alloc` prefixes in generated code
///
/// This option also forces Btree everywhere, overriding the BtreeMap options,
/// since HashMap is not in alloc::collections, only std (it requires randomness)
///
#[derive(PartialEq)]
pub enum CollectionsLib {
Std,
Alloc,
}

impl CollectionsLib {
pub fn to_str(&self) -> &'static str {
match self {
CollectionsLib::Std => { "::std" },
CollectionsLib::Alloc => { "::alloc" },
}
}
}

/// Configuration options for Protobuf code generation.
///
/// This configuration builder can be used to set non-default code generation options.
pub struct Config {
service_generator: Option<Box<dyn ServiceGenerator>>,
collections_lib: CollectionsLib,
btree_map: Vec<String>,
type_attributes: Vec<(String, String)>,
field_attributes: Vec<(String, String)>,
Expand Down Expand Up @@ -460,6 +481,15 @@ impl Config {
self
}

/// Configure the code generator to use the `::alloc` namespace rather than `::std`, and Btree everywhere
/// rather than `std`.
///
/// This allows generated code to be used in a `#![no_std]` crate
pub fn use_alloc_collections_lib(&mut self) -> &mut Self {
self.collections_lib = CollectionsLib::Alloc;
self
}

/// Configures the output directory where generated Rust files will be written.
///
/// If unset, defaults to the `OUT_DIR` environment variable. `OUT_DIR` is set by Cargo when
Expand Down Expand Up @@ -581,6 +611,7 @@ impl default::Default for Config {
fn default() -> Config {
Config {
service_generator: None,
collections_lib: CollectionsLib::Std,
btree_map: Vec::new(),
type_attributes: Vec::new(),
field_attributes: Vec::new(),
Expand Down
18 changes: 11 additions & 7 deletions src/encoding.rs
Expand Up @@ -2,10 +2,10 @@
//!
//! Meant to be used only from `Message` implementations.

use std::cmp::min;
use std::mem;
use std::u32;
use std::usize;
use core::cmp::min;
use core::mem;
use core::u32;
use core::usize;

use ::bytes::{Buf, BufMut};

Expand Down Expand Up @@ -1040,10 +1040,8 @@ pub mod group {
/// generic over `HashMap` and `BTreeMap`.
macro_rules! map {
($map_ty:ident) => {
use std::collections::$map_ty;
use std::hash::Hash;

use crate::encoding::*;
use core::hash::Hash;

/// Generic protobuf map encode function.
pub fn encode<K, V, B, KE, KL, VE, VL>(
Expand Down Expand Up @@ -1225,11 +1223,17 @@ macro_rules! map {
};
}

#[cfg(feature = "std")]
pub mod hash_map {
use std::collections::HashMap;
map!(HashMap);
}

pub mod btree_map {
#[cfg(feature = "std")]
use std::collections::BTreeMap;
#[cfg(not(feature = "std"))]
use alloc::collections::BTreeMap;
map!(BTreeMap);
}

Expand Down
15 changes: 14 additions & 1 deletion src/error.rs
@@ -1,8 +1,17 @@
//! Protobuf encoding and decoding errors.

// Note: this can always be alloc::borrow::Cow with rust >= 1.36 because alloc was stabilized
#[cfg(feature = "std")]
use std::borrow::Cow;
#[cfg(not(feature = "std"))]
use alloc::borrow::Cow;

#[cfg(feature = "std")]
use std::error;
use std::fmt;

use core::fmt;

#[cfg(feature = "std")]
use std::io;

/// A Protobuf message decoding error.
Expand Down Expand Up @@ -54,12 +63,14 @@ impl fmt::Display for DecodeError {
}
}

#[cfg(feature = "std")]
impl error::Error for DecodeError {
fn description(&self) -> &str {
&self.description
}
}

#[cfg(feature = "std")]
impl From<DecodeError> for io::Error {
fn from(error: DecodeError) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, error)
Expand Down Expand Up @@ -108,12 +119,14 @@ impl fmt::Display for EncodeError {
}
}

#[cfg(feature = "std")]
impl error::Error for EncodeError {
fn description(&self) -> &str {
"failed to encode Protobuf message: insufficient buffer capacity"
}
}

#[cfg(feature = "std")]
impl From<EncodeError> for io::Error {
fn from(error: EncodeError) -> io::Error {
io::Error::new(io::ErrorKind::InvalidInput, error)
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
@@ -1,5 +1,7 @@
#![doc(html_root_url = "https://docs.rs/prost/0.5.0")]

#![cfg_attr(not(feature = "std"), no_std)]

mod error;
mod message;
mod types;
Expand Down
4 changes: 2 additions & 2 deletions src/message.rs
@@ -1,5 +1,5 @@
use std::fmt::Debug;
use std::usize;
use core::fmt::Debug;
use core::usize;

use ::bytes::{Buf, BufMut, IntoBuf};

Expand Down
34 changes: 34 additions & 0 deletions tests-alloc/Cargo.toml
@@ -0,0 +1,34 @@
[package]
name = "tests-alloc"
version = "0.0.0"
authors = ["Dan Burkert <dan@danburkert.com>"]
publish = false
edition = "2018"

build = "../tests/src/build.rs"

[lib]
doctest = false
path = "../tests/src/lib.rs"

[features]
default = ["nostd-collections"]
nostd-collections = []

[dependencies]
bytes = "0.4.7"
cfg-if = "0.1"
prost = { path = ".." }
prost-types = { path = "../prost-types" }
protobuf = { path = "../protobuf" }

[dev-dependencies]
diff = "0.1"
prost-build = { path = "../prost-build" }
tempfile = "3"

[build-dependencies]
cfg-if = "0.1"
env_logger = { version = "0.6", default-features = false }
prost-build = { path = "../prost-build" }
protobuf = { path = "../protobuf" }
13 changes: 11 additions & 2 deletions tests/src/build.rs
Expand Up @@ -20,12 +20,21 @@ fn main() {
let src = PathBuf::from("../tests/src");
let includes = &[src.clone()];

let mut config = prost_build::Config::new();

// Generate BTreeMap fields for all messages. This forces encoded output to be consistent, so
// that encode/decode roundtrips can use encoded output for comparison. Otherwise trying to
// compare based on the Rust PartialEq implementations is difficult, due to presence of NaN
// values.
let mut config = prost_build::Config::new();
config.btree_map(&["."]);
//
// Note nostd collections implies Btree everywhere anyways
cfg_if! {
if #[cfg(feature = "nostd-collections")] {
config.use_alloc_collections_lib();
} else {
config.btree_map(&["."]);
}
}
// Tests for custom attributes
config.type_attribute("Foo.Bar_Baz.Foo_barBaz", "#[derive(Eq, PartialOrd, Ord)]");
config.type_attribute(
Expand Down

0 comments on commit e9f2c51

Please sign in to comment.