Skip to content

Commit fc9fbd9

Browse files
authored
build: Remove bundled protoc and build from source (tokio-rs#610)
1 parent 2f6b6f1 commit fc9fbd9

35 files changed

+222
-171
lines changed

.github/workflows/continuous-integration-workflow.yaml

+27-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ jobs:
88
steps:
99
- name: checkout
1010
uses: actions/checkout@v2
11+
with:
12+
submodules: recursive
1113
- name: install toolchain
1214
uses: actions-rs/toolchain@v1
1315
with:
@@ -24,7 +26,10 @@ jobs:
2426
clippy:
2527
runs-on: ubuntu-latest
2628
steps:
27-
- uses: actions/checkout@v2
29+
- name: checkout
30+
uses: actions/checkout@v2
31+
with:
32+
submodules: recursive
2833
- name: install toolchain
2934
uses: actions-rs/toolchain@v1
3035
with:
@@ -54,6 +59,8 @@ jobs:
5459
steps:
5560
- name: checkout
5661
uses: actions/checkout@v2
62+
with:
63+
submodules: recursive
5764
- name: install toolchain
5865
uses: actions-rs/toolchain@v1
5966
with:
@@ -78,6 +85,8 @@ jobs:
7885
steps:
7986
- name: checkout
8087
uses: actions/checkout@v2
88+
with:
89+
submodules: recursive
8190
- name: install toolchain
8291
uses: actions-rs/toolchain@v1
8392
with:
@@ -109,3 +118,20 @@ jobs:
109118
with:
110119
command: check
111120
args: --manifest-path prost-build/Cargo.toml
121+
122+
vendored:
123+
runs-on: ubuntu-latest
124+
steps:
125+
- name: checkout
126+
uses: actions/checkout@v2
127+
with:
128+
submodules: recursive
129+
- name: install toolchain
130+
uses: actions-rs/toolchain@v1
131+
with:
132+
toolchain: stable
133+
default: true
134+
profile: minimal
135+
- name: cargo check
136+
run: cd test-vendored && cargo check
137+

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "prost-build/third-party/protobuf"]
2+
path = prost-build/third-party/protobuf
3+
url = [email protected]:protocolbuffers/protobuf

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ exclude = [
3333
"fuzz",
3434
# Same counts for the afl fuzz targets
3535
"afl",
36+
"test-vendored"
3637
]
3738

3839
[lib]

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ start-to-finish example.
4747
`proto3` syntax. `prost`'s goal is to make the generated code as simple as
4848
possible.
4949

50+
### `protoc`
51+
52+
It's recommended to install `protoc` locally in your path to improve build times.
53+
Prost uses `protoc` to parse protobuf files and will attempt to compile protobuf
54+
from source requiring a C++ toolchain. For more info checkout the [`prost-build`](prost-build)
55+
docs.
56+
5057
### Packages
5158

5259
Prost can now generate code for `.proto` files that don't have a package spec.

prost-build/Cargo.toml

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ readme = "README.md"
1212
description = "A Protocol Buffers implementation for the Rust Language."
1313
edition = "2018"
1414

15+
[features]
16+
default = []
17+
vendored = []
18+
1519
[dependencies]
1620
bytes = { version = "1", default-features = false }
1721
heck = "0.4"
@@ -27,6 +31,8 @@ regex = { version = "1.5.5", default-features = false, features = ["std", "unico
2731

2832
[build-dependencies]
2933
which = { version = "4", default-features = false }
34+
cfg-if = "1"
35+
cmake = "0.1"
3036

3137
[dev-dependencies]
3238
env_logger = { version = "0.8", default-features = false }

prost-build/README.md

+22
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,28 @@
77
a Cargo build. See the crate [documentation](https://docs.rs/prost-build/) for examples
88
of how to integrate `prost-build` into a Cargo project.
99

10+
## `protoc`
11+
12+
`prost-build` uses `protoc` to parse the proto files. There are a few ways to make `protoc`
13+
available for `prost-build`.
14+
15+
The first option is to include `protoc` in your `PATH` this
16+
can be done by following the [`protoc` install instructions]. In addition, its possible to
17+
pass the `PROTOC=<my/path/to/protoc>` environment variable.
18+
19+
[`protoc` install instructions]: https://github.com/protocolbuffers/protobuf#protocol-compiler-installation
20+
21+
The second option is to provide the `vendored` feature flag to `prost-build`. This will
22+
force `prost-build` to compile `protoc` from the bundled source. This will require that
23+
you have the correct dependencies installed include a C++ toolchain, cmake, etc. For
24+
more info on what the required dependencies are check [here].
25+
26+
[here]: https://github.com/protocolbuffers/protobuf/blob/master/src/README.md
27+
28+
If you would like to always ignore vendoring `protoc` you can additionally pass
29+
`PROTOC_NO_VENDOR` and this will always check the `PATH`/`PROTOC` environment
30+
variables and never compile `protoc` from source.
31+
1032
## License
1133

1234
`prost-build` is distributed under the terms of the Apache License (Version 2.0).

prost-build/build.rs

+60-61
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,29 @@
11
//! Finds the appropriate `protoc` binary and Protobuf include directory for this host, and outputs
22
//! build directives so that the main `prost-build` crate can use them.
33
//!
4-
//! The following locations are checked for `protoc` in decreasing priority:
4+
//! This build script attempts to find `protoc` in a few ways:
55
//!
6-
//! 1. The `PROTOC` environment variable.
7-
//! 2. The bundled `protoc`.
8-
//! 3. The `protoc` on the `PATH`.
9-
//!
10-
//! If no `protoc` binary is available in these locations, the build fails.
6+
//! 1. If `PROTOC_NO_VENDOR` is enabled, it will check the `PROTOC` environment variable
7+
//! then check the `PATH` for a `protoc` or `protoc.exe`.
8+
//! 2. If the `vendored` feature flag is enabled or `protoc` can't be found via the environment
9+
//! variable or in the `PATH` then `prost-build` will attempt to build `protoc` from the
10+
//! bundled source code.
11+
//! 3. Otherwise, it will attempt to execute from the `PATH` and fail if it does not exist.
1112
//!
1213
//! The following locations are checked for the Protobuf include directory in decreasing priority:
1314
//!
1415
//! 1. The `PROTOC_INCLUDE` environment variable.
1516
//! 2. The bundled Protobuf include directory.
17+
//!
1618
19+
use cfg_if::cfg_if;
1720
use std::env;
1821
use std::path::PathBuf;
22+
use which::which;
1923

2024
/// Returns the path to the location of the bundled Protobuf artifacts.
2125
fn bundle_path() -> PathBuf {
22-
env::current_dir()
23-
.unwrap()
24-
.join("third-party")
25-
.join("protobuf")
26-
}
27-
28-
/// Returns the path to the `protoc` pointed to by the `PROTOC` environment variable, if it is set.
29-
fn env_protoc() -> Option<PathBuf> {
30-
let protoc = match env::var_os("PROTOC") {
31-
Some(path) => PathBuf::from(path),
32-
None => return None,
33-
};
34-
35-
Some(protoc)
36-
}
37-
38-
/// We can only use a bundled protoc if the interpreter necessary to load the binary is available.
39-
///
40-
/// The interpreter is specific to the binary and can be queried via e.g. `patchelf
41-
/// --print-interpreter`, or via readelf, or similar.
42-
fn is_interpreter(path: &'static str) -> bool {
43-
// Here we'd check for it being executable and other things, but for now it being present is
44-
// probably good enough.
45-
std::fs::metadata(path).is_ok()
46-
}
47-
48-
/// Returns the path to the bundled `protoc`, if it is available for the host platform.
49-
fn bundled_protoc() -> Option<PathBuf> {
50-
let protoc_bin_name = match (env::consts::OS, env::consts::ARCH) {
51-
("linux", "x86") if is_interpreter("/lib/ld-linux.so.2") => "protoc-linux-x86_32",
52-
("linux", "x86_64") if is_interpreter("/lib64/ld-linux-x86-64.so.2") => {
53-
"protoc-linux-x86_64"
54-
}
55-
("linux", "aarch64") if is_interpreter("/lib/ld-linux-aarch64.so.1") => {
56-
"protoc-linux-aarch_64"
57-
}
58-
("macos", "x86_64") => "protoc-osx-x86_64",
59-
("macos", "aarch64") => "protoc-osx-aarch64",
60-
("windows", _) => "protoc-win32.exe",
61-
_ => return None,
62-
};
63-
64-
Some(bundle_path().join(protoc_bin_name))
65-
}
66-
67-
/// Returns the path to the `protoc` included on the `PATH`, if it exists.
68-
fn path_protoc() -> Option<PathBuf> {
69-
which::which("protoc").ok()
26+
env::current_dir().unwrap().join("third-party")
7027
}
7128

7229
/// Returns the path to the Protobuf include directory pointed to by the `PROTOC_INCLUDE`
@@ -98,14 +55,56 @@ fn bundled_protoc_include() -> PathBuf {
9855
bundle_path().join("include")
9956
}
10057

58+
/// Check for `protoc` via the `PROTOC` env var or in the `PATH`.
59+
fn path_protoc() -> Option<PathBuf> {
60+
env::var_os("PROTOC")
61+
.map(PathBuf::from)
62+
.or_else(|| which("protoc").ok())
63+
}
64+
65+
/// Returns true if the vendored flag is enabled.
66+
fn vendored() -> bool {
67+
cfg_if! {
68+
if #[cfg(feature = "vendored")] {
69+
true
70+
} else {
71+
false
72+
}
73+
}
74+
}
75+
76+
/// Compile `protoc` via `cmake`.
77+
fn compile() -> Option<PathBuf> {
78+
let protobuf_src = bundle_path().join("protobuf").join("cmake");
79+
80+
println!("cargo:rerun-if-changed={}", protobuf_src.display());
81+
82+
let dst = cmake::Config::new(protobuf_src).build();
83+
84+
Some(dst.join("bin").join("protoc"))
85+
}
86+
87+
/// Try to find a `protoc` through a few methods.
88+
///
89+
/// Check module docs for more info.
90+
fn protoc() -> Option<PathBuf> {
91+
if env::var_os("PROTOC_NO_VENDOR").is_some() {
92+
path_protoc()
93+
} else if vendored() {
94+
compile()
95+
} else {
96+
path_protoc().or_else(compile)
97+
}
98+
}
99+
101100
fn main() {
102-
let protoc = env_protoc()
103-
.or_else(bundled_protoc)
104-
.or_else(path_protoc)
105-
.expect(
106-
"Failed to find the protoc binary. The PROTOC environment variable is not set, \
107-
there is no bundled protoc for this platform, and protoc is not in the PATH",
108-
);
101+
let protoc = protoc().expect(
102+
"Failed to find or build the protoc binary. The PROTOC environment \
103+
is not set, `protoc` is not in PATH or you are missing the requirements to compile protobuf \
104+
from source. \n \
105+
Check out the `prost-build` README for instructions on the requirements: \
106+
https://github.com/tokio-rs/prost#generated-code",
107+
);
109108

110109
let protoc_include = env_protoc_include().unwrap_or_else(bundled_protoc_include);
111110

prost-build/src/lib.rs

+18-7
Original file line numberDiff line numberDiff line change
@@ -97,17 +97,28 @@
9797
//! PROTOC_INCLUDE=/usr/include
9898
//! ```
9999
//!
100-
//! If `PROTOC` is not found in the environment, then a pre-compiled `protoc` binary bundled in the
101-
//! prost-build crate is used. Pre-compiled `protoc` binaries exist for Linux (non-musl), macOS,
102-
//! and Windows systems. If no pre-compiled `protoc` is available for the host platform, then the
103-
//! `protoc` or `protoc.exe` binary on the `PATH` is used. If `protoc` is not available in any of
104-
//! these fallback locations, then the build fails.
100+
//! If no `PROTOC` environment variable is set then `prost-build` will search the
101+
//! current path for `protoc` or `protoc.exe`. If `protoc` is not found via these
102+
//! two methods then `prost-build` will attempt to compile `protoc` from the bundled
103+
//! source.
104+
//!
105+
//! If you would not like `prost-build` to not compile `protoc` from source ever then
106+
//! ensure you have set `PROTO_NO_VENDOR` environment variable as this will disable
107+
//! compiling from source even if the `vendored` feature flag is enabled.
108+
//!
109+
//! If you would like to always compile from source then setting the `vendored` feature
110+
//! flag will force `prost-build` to always build `protoc` from source.
105111
//!
106112
//! If `PROTOC_INCLUDE` is not found in the environment, then the Protobuf include directory
107113
//! bundled in the prost-build crate is be used.
108114
//!
109-
//! To force `prost-build` to use the `protoc` on the `PATH`, add `PROTOC=protoc` to the
110-
//! environment.
115+
//! ### Compiling `protoc` from source
116+
//!
117+
//! Compiling `protoc` from source requires a few external dependencies. Currently,
118+
//! `prost-build` uses `cmake` to build `protoc`. For more information check out the
119+
//! [protobuf build instructions](protobuf-build).
120+
//!
121+
//! [protobuf-build]: https://github.com/protocolbuffers/protobuf/blob/master/src/README.md
111122
112123
mod ast;
113124
mod code_generator;

prost-build/third-party/protobuf/include/google/protobuf/descriptor.proto renamed to prost-build/third-party/include/google/protobuf/descriptor.proto

+10-8
Original file line numberDiff line numberDiff line change
@@ -348,17 +348,17 @@ message FileOptions {
348348
optional string java_package = 1;
349349

350350

351-
// If set, all the classes from the .proto file are wrapped in a single
352-
// outer class with the given name. This applies to both Proto1
353-
// (equivalent to the old "--one_java_file" option) and Proto2 (where
354-
// a .proto always translates to a single class, but you may want to
355-
// explicitly choose the class name).
351+
// Controls the name of the wrapper Java class generated for the .proto file.
352+
// That class will always contain the .proto file's getDescriptor() method as
353+
// well as any top-level extensions defined in the .proto file.
354+
// If java_multiple_files is disabled, then all the other classes from the
355+
// .proto file will be nested inside the single wrapper outer class.
356356
optional string java_outer_classname = 8;
357357

358-
// If set true, then the Java code generator will generate a separate .java
358+
// If enabled, then the Java code generator will generate a separate .java
359359
// file for each top-level message, enum, and service defined in the .proto
360-
// file. Thus, these types will *not* be nested inside the outer class
361-
// named by java_outer_classname. However, the outer class will still be
360+
// file. Thus, these types will *not* be nested inside the wrapper class
361+
// named by java_outer_classname. However, the wrapper class will still be
362362
// generated to contain the file's getDescriptor() method as well as any
363363
// top-level extensions defined in the file.
364364
optional bool java_multiple_files = 10 [default = false];
@@ -496,6 +496,8 @@ message MessageOptions {
496496
// this is a formalization for deprecating messages.
497497
optional bool deprecated = 3 [default = false];
498498

499+
reserved 4, 5, 6;
500+
499501
// Whether the message is an automatically generated map entry type for the
500502
// maps field.
501503
//

prost-build/third-party/protobuf/include/google/protobuf/struct.proto renamed to prost-build/third-party/include/google/protobuf/struct.proto

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ message Struct {
5555

5656
// `Value` represents a dynamically typed value which can be either
5757
// null, a number, a string, a boolean, a recursive struct value, or a
58-
// list of values. A producer of value is expected to set one of that
59-
// variants, absence of any variant indicates an error.
58+
// list of values. A producer of value is expected to set one of these
59+
// variants. Absence of any variant indicates an error.
6060
//
6161
// The JSON representation for `Value` is JSON value.
6262
message Value {

prost-build/third-party/protobuf

Submodule protobuf added at 22d0e26

prost-build/third-party/protobuf/LICENSE

-32
This file was deleted.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-2.59 MB
Binary file not shown.

0 commit comments

Comments
 (0)