proto_build_tools/
lib.rs

1// Copyright 2022 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Contains utilities to build protos & make them available to Rust.
6
7use std::collections::HashSet;
8use std::fs;
9use std::fs::File;
10use std::io::Write;
11use std::path::PathBuf;
12
13use protobuf_codegen::Codegen;
14
15/// Builds a set of Rust protos based on the provided proto files. The individual protos will be
16/// dumped into `out_dir` (will be created if needed), along with a file that wraps them
17/// `out_dir/generated.rs`. The wrapper file can then be included using a pattern like:
18/// ```ignore
19/// pub mod protos {
20///    // Suppose the `out_dir` supplied to `build_protos` was
21///    // format!("{}/my_crate_protos", env!("OUT_DIR"))
22///    include!(concat!(env!("OUT_DIR"), "/my_crate_protos/generated.rs"));
23/// }
24/// // Protos are available at protos::proto_file_name.
25/// ```
26pub fn build_protos(out_dir: &PathBuf, proto_paths: &[PathBuf]) {
27    build_protos_explicit(
28        out_dir,
29        proto_paths,
30        proto_paths,
31        to_includes(proto_paths).as_slice(),
32    )
33}
34
35/// Allows for more control than build_protos (useful when the proto build is more complex).
36pub fn build_protos_explicit(
37    out_dir: &PathBuf,
38    proto_paths: &[PathBuf],
39    rebuild_if_changed_paths: &[PathBuf],
40    includes: &[PathBuf],
41) {
42    for file in rebuild_if_changed_paths {
43        // Triggers rebuild if the file has newer mtime.
44        println!(
45            "cargo:rerun-if-changed={}",
46            file.to_str().expect("proto path must be UTF-8")
47        );
48    }
49    fs::create_dir_all(out_dir).unwrap();
50    if !proto_paths.is_empty() {
51        gen_protos(out_dir, proto_paths, includes);
52    }
53    create_gen_file(out_dir, proto_paths);
54}
55
56/// Given a list of proto files, extract the set of include directories needed to pass to the proto
57/// compiler.
58fn to_includes(proto_paths: &[PathBuf]) -> Vec<PathBuf> {
59    let mut include_paths = HashSet::new();
60
61    for proto in proto_paths {
62        include_paths.insert(
63            proto
64                .parent()
65                .expect("protos must be files in a directory")
66                .to_owned(),
67        );
68    }
69
70    include_paths.drain().collect::<Vec<PathBuf>>()
71}
72
73fn gen_protos(out_dir: &PathBuf, proto_paths: &[PathBuf], includes: &[PathBuf]) {
74    Codegen::new()
75        .out_dir(out_dir)
76        .inputs(proto_paths)
77        .includes(includes)
78        .run()
79        .expect("failed to compile Rust protos");
80}
81
82fn create_gen_file(out_dir: &PathBuf, proto_files: &[PathBuf]) {
83    let generated = PathBuf::from(&out_dir).join("generated.rs");
84    let out = File::create(generated).expect("Failed to create generated file.");
85
86    for proto_path in proto_files {
87        let file_stem = proto_path.file_stem().unwrap().to_str().unwrap();
88        let out_dir = out_dir
89            .to_str()
90            .expect("path must be UTF-8")
91            .replace('\\', "/");
92        writeln!(&out, "#[path = \"{out_dir}/{file_stem}.rs\"]")
93            .expect("failed to write to generated.");
94        writeln!(&out, "pub mod {file_stem};").expect("failed to write to generated.");
95    }
96}