Skip to content

Commit

Permalink
Add no_std support to prost, prost-types & make codegen `no_std…
Browse files Browse the repository at this point in the history
…` compatible

This PR adds a new `std` feature to the `prost` and `prost-types`
crates, which is enabled by default. Additionally, the `prost-build` and
`prost-derive` crates have been updated to emit no-std compatible code.

This is a breaking change for edition-2015 crates, which are likely to
now require an `extern crate core;` directive when including
`prost`-generated code.

Co-authored-by: Chris Beck <beck.ct@gmail.com>
Co-authored-by: Dan Burkert <dan@danburkert.com>
Co-authored-by: David Flemström <david.flemstrom@gmail.com>
  • Loading branch information
3 people committed May 10, 2020
1 parent 2de785a commit fdf9fdf
Show file tree
Hide file tree
Showing 32 changed files with 545 additions and 379 deletions.
44 changes: 39 additions & 5 deletions .github/workflows/continuous-integration-workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ jobs:
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
default: true
profile: minimal
components: rustfmt
- name: rustfmt
uses: actions-rs/cargo@v1
Expand All @@ -28,7 +29,8 @@ jobs:
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
default: true
profile: minimal
components: clippy
- name: install ninja
uses: seanmiddleditch/gha-setup-ninja@v1
Expand All @@ -45,7 +47,7 @@ jobs:
uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --workspace --all-targets
args: --workspace --all-features --all-targets

test:
runs-on: ${{ matrix.os }}
Expand All @@ -65,8 +67,8 @@ jobs:
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.toolchain }}
override: true
components: rustfmt
default: true
profile: minimal
- name: install ninja
uses: seanmiddleditch/gha-setup-ninja@v1
- name: cache target directory
Expand All @@ -82,3 +84,35 @@ jobs:
with:
command: test
args: --workspace --all-targets
- name: test no-default-features
uses: actions-rs/cargo@v1
with:
command: test
args: --no-default-features

no-std:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2
- name: install toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
default: true
profile: minimal
- name: install cargo-no-std-check
uses: actions-rs/cargo@v1
with:
command: install
args: cargo-no-std-check
- name: prost cargo-no-std-check
uses: actions-rs/cargo@v1
with:
command: no-std-check
args: --manifest-path Cargo.toml --no-default-features
- name: prost-types cargo-no-std-check
uses: actions-rs/cargo@v1
with:
command: no-std-check
args: --manifest-path prost-types/Cargo.toml --no-default-features
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ members = [
"protobuf",
"tests",
"tests-2015",
"tests-no-std",
]
exclude = [
# The fuzz crate can't be compiled or tested without the 'cargo fuzz' command,
Expand All @@ -36,11 +37,12 @@ exclude = [
bench = false

[features]
default = ["prost-derive"]
default = ["prost-derive", "std"]
no-recursion-limit = []
std = []

[dependencies]
bytes = "0.5"
bytes = { version = "0.5", default-features = false }
prost-derive = { version = "0.6.1", path = "prost-derive", optional = true }

[dev-dependencies]
Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,29 @@ pub struct AddressBook {
}
```

## Using `prost` in a `no_std` Crate

`prost` is compatible with `no_std` crates. To enable `no_std` support, disable
the `std` features in `prost` and `prost-types`:

```
[dependencies]
prost = { version = "0.6", default-features = false, features = ["prost-derive"] }
# Only necessary if using Protobuf well-known types:
prost-types = { version = "0.6", default-features = false }
```

Additionally, configure `prost-buid` to output `BTreeMap`s instead of `HashMap`s
for all Protobuf `map` fields in your `build.rs`:

```rust
let mut config = prost_build::Config::new();
config.btree_map(&["."]);
```

When using edition 2015, it may be necessary to add an `extern crate core;`
directive to the crate which includes `prost`-generated code.

## Serializing Existing Types

`prost` uses a custom derive macro to handle encoding and decoding types, which
Expand Down
6 changes: 3 additions & 3 deletions prost-build/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ description = "A Protocol Buffers implementation for the Rust Language."
edition = "2018"

[dependencies]
bytes = "0.5"
bytes = { version = "0.5", default-features = false }
heck = "0.3"
itertools = "0.8"
log = "0.4"
multimap = { version = "0.8", default-features = false }
petgraph = { version = "0.5", default-features = false }
prost = { version = "0.6.1", path = ".." }
prost-types = { version = "0.6.1", path = "../prost-types" }
prost = { version = "0.6.1", path = "..", default-features = false }
prost-types = { version = "0.6.1", path = "../prost-types", default-features = false }
tempfile = "3"

[build-dependencies]
Expand Down
28 changes: 16 additions & 12 deletions prost-build/src/code_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,12 +369,12 @@ 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("::prost::alloc::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("::prost::alloc::boxed::Box<");
}
self.buf.push_str(&ty);
if boxed {
Expand Down Expand Up @@ -411,10 +411,10 @@ impl<'a> CodeGenerator<'a> {
.btree_map
.iter()
.any(|matcher| match_ident(matcher, msg_name, Some(field.name())));
let (annotation_ty, rust_ty) = if btree_map {
("btree_map", "BTreeMap")
let (annotation_ty, lib_name, rust_ty) = if btree_map {
("btree_map", "::prost::alloc::collections", "BTreeMap")
} else {
("map", "HashMap")
("map", "::std::collections", "HashMap")
};

let key_tag = self.field_type_tag(key);
Expand All @@ -429,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 {}: {}::{}<{}, {}>,\n",
to_snake(field.name()),
lib_name,
rust_ty,
key_ty,
value_ty
Expand Down Expand Up @@ -462,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 @@ -523,8 +524,11 @@ impl<'a> CodeGenerator<'a> {
);

if boxed {
self.buf
.push_str(&format!("{}(Box<{}>),\n", to_upper_camel(field.name()), ty));
self.buf.push_str(&format!(
"{}(::prost::alloc::boxed::Box<{}>),\n",
to_upper_camel(field.name()),
ty
));
} else {
self.buf
.push_str(&format!("{}({}),\n", to_upper_camel(field.name()), ty));
Expand Down Expand Up @@ -714,8 +718,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 => String::from("::prost::alloc::string::String"),
Type::Bytes => String::from("::prost::alloc::vec::Vec<u8>"),
Type::Group | Type::Message => self.resolve_ident(field.type_name()),
}
}
Expand Down
2 changes: 1 addition & 1 deletion prost-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ impl Config {
/// // Match all map fields in a package.
/// config.btree_map(&[".my_messages"]);
///
/// // Match all map fields.
/// // Match all map fields. Expecially useful in `no_std` contexts.
/// config.btree_map(&["."]);
///
/// // Match all map fields in a nested message.
Expand Down
2 changes: 1 addition & 1 deletion prost-derive/src/field/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ impl Field {

pub fn clear(&self, ident: TokenStream) -> TokenStream {
match self.label {
Label::Optional => quote!(#ident = ::std::option::Option::None),
Label::Optional => quote!(#ident = ::core::option::Option::None),
Label::Required => quote!(#ident.clear()),
Label::Repeated => quote!(#ident.clear()),
}
Expand Down
25 changes: 17 additions & 8 deletions prost-derive/src/field/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ impl MapTy {
MapTy::BTreeMap => Ident::new("btree_map", Span::call_site()),
}
}

fn lib(&self) -> TokenStream {
match self {
MapTy::HashMap => quote! { std },
MapTy::BTreeMap => quote! { prost::alloc },
}
}
}

fn fake_scalar(ty: scalar::Ty) -> scalar::Field {
Expand Down Expand Up @@ -271,11 +278,11 @@ impl Field {
let insert_doc = format!("Inserts a key value pair into `{}`.", ident);
Some(quote! {
#[doc=#get_doc]
pub fn #get(&self, key: #key_ref_ty) -> ::std::option::Option<#ty> {
pub fn #get(&self, key: #key_ref_ty) -> ::core::option::Option<#ty> {
self.#ident.get(#take_ref key).cloned().and_then(#ty::from_i32)
}
#[doc=#insert_doc]
pub fn #insert(&mut self, key: #key_ty, value: #ty) -> ::std::option::Option<#ty> {
pub fn #insert(&mut self, key: #key_ty, value: #ty) -> ::core::option::Option<#ty> {
self.#ident.insert(key, value as i32).and_then(#ty::from_i32)
}
})
Expand All @@ -293,12 +300,14 @@ impl Field {
MapTy::HashMap => Ident::new("HashMap", Span::call_site()),
MapTy::BTreeMap => Ident::new("BTreeMap", Span::call_site()),
};

// A fake field for generating the debug wrapper
let key_wrapper = fake_scalar(self.key_ty.clone()).debug(quote!(KeyWrapper));
let key = self.key_ty.rust_type();
let value_wrapper = self.value_ty.debug();
let libname = self.map_ty.lib();
let fmt = quote! {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
#key_wrapper
#value_wrapper
let mut builder = f.debug_map();
Expand All @@ -312,17 +321,17 @@ impl Field {
ValueTy::Scalar(ty) => {
let value = ty.rust_type();
quote! {
struct #wrapper_name<'a>(&'a ::std::collections::#type_name<#key, #value>);
impl<'a> ::std::fmt::Debug for #wrapper_name<'a> {
struct #wrapper_name<'a>(&'a ::#libname::collections::#type_name<#key, #value>);
impl<'a> ::core::fmt::Debug for #wrapper_name<'a> {
#fmt
}
}
}
ValueTy::Message => quote! {
struct #wrapper_name<'a, V: 'a>(&'a ::std::collections::#type_name<#key, V>);
impl<'a, V> ::std::fmt::Debug for #wrapper_name<'a, V>
struct #wrapper_name<'a, V: 'a>(&'a ::#libname::collections::#type_name<#key, V>);
impl<'a, V> ::core::fmt::Debug for #wrapper_name<'a, V>
where
V: ::std::fmt::Debug + 'a,
V: ::core::fmt::Debug + 'a,
{
#fmt
}
Expand Down
2 changes: 1 addition & 1 deletion prost-derive/src/field/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ impl Field {

pub fn clear(&self, ident: TokenStream) -> TokenStream {
match self.label {
Label::Optional => quote!(#ident = ::std::option::Option::None),
Label::Optional => quote!(#ident = ::core::option::Option::None),
Label::Required => quote!(#ident.clear()),
Label::Repeated => quote!(#ident.clear()),
}
Expand Down
2 changes: 1 addition & 1 deletion prost-derive/src/field/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ impl Field {
pub fn default(&self) -> TokenStream {
match *self {
Field::Scalar(ref scalar) => scalar.default(),
_ => quote!(::std::default::Default::default()),
_ => quote!(::core::default::Default::default()),
}
}

Expand Down
2 changes: 1 addition & 1 deletion prost-derive/src/field/oneof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,6 @@ impl Field {
}

pub fn clear(&self, ident: TokenStream) -> TokenStream {
quote!(#ident = ::std::option::Option::None)
quote!(#ident = ::core::option::Option::None)
}
}

0 comments on commit fdf9fdf

Please sign in to comment.