xref: /aosp_15_r20/external/crosvm/proto_build_tools/src/lib.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
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 
7 use std::collections::HashSet;
8 use std::fs;
9 use std::fs::File;
10 use std::io::Write;
11 use std::path::PathBuf;
12 
13 use 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 /// ```
build_protos(out_dir: &PathBuf, proto_paths: &[PathBuf])26 pub 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).
build_protos_explicit( out_dir: &PathBuf, proto_paths: &[PathBuf], rebuild_if_changed_paths: &[PathBuf], includes: &[PathBuf], )36 pub 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.
to_includes(proto_paths: &[PathBuf]) -> Vec<PathBuf>58 fn 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 
gen_protos(out_dir: &PathBuf, proto_paths: &[PathBuf], includes: &[PathBuf])73 fn 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 
create_gen_file(out_dir: &PathBuf, proto_files: &[PathBuf])82 fn 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 = \"{}/{}.rs\"]", out_dir, file_stem)
93             .expect("failed to write to generated.");
94         writeln!(&out, "pub mod {};", file_stem).expect("failed to write to generated.");
95     }
96 }
97