1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
use crate::config;
use crate::error;

use protobuf;
use std::cmp;
use std::fs;
use std::path;
use std::process;

pub fn add_file(
    paths: &config::Paths,
    relative_to: &path::Path,
    file: &path::Path,
) -> error::Result<()> {
    let rel_file = file
        .strip_prefix(relative_to)
        .unwrap_or_else(|_| file.file_name().map_or(file, path::Path::new));
    let target = paths.preferred_data("proto").join(rel_file);

    if let Some(parent) = target.parent() {
        trace!("Creating directory {:?}", parent);
        fs::create_dir_all(parent)?;
    }

    fs::copy(file, &target)?;
    info!("Added proto file as {:?}", target);
    Ok(())
}

pub fn compile_descriptor_set(
    paths: &config::Paths,
) -> error::Result<protobuf::descriptor::FileDescriptorSet> {
    let proto_includes = paths.find_data("proto")?;
    let proto_files = paths.find_data("proto/**/*.proto")?;
    let cache = paths.preferred_cache("descriptor-cache.pb");

    debug!("Proto includes: {:?}", proto_includes);
    debug!("Proto files: {:?}", proto_files);
    debug!("Proto cache location: {:?}", cache);

    if is_cache_stale(&cache, &proto_files)? {
        info!("Proto descriptor cache is stale; recomputing");

        if let Some(parent) = cache.parent() {
            trace!("Creating directory {:?}", parent);
            fs::create_dir_all(parent)?;
        }

        let include_args = proto_includes
            .into_iter()
            .map(|p| format!("-I{}", p.to_string_lossy()))
            .collect::<Vec<_>>();

        let status = process::Command::new("protoc")
            .arg("-o")
            .arg(&cache)
            .args(&include_args)
            .args(&proto_files)
            .status()?;
        if !status.success() {
            panic!("protoc descriptor compilation failed");
        }

        trace!("Proto descriptor cache regenerated");
    }

    let mut cache_file = fs::File::open(&cache)?;
    let descriptor_set = protobuf::Message::parse_from_reader(&mut cache_file)?;

    trace!("Successfully parsed descriptor set from cache");

    Ok(descriptor_set)
}

fn is_cache_stale<P>(cache: &path::Path, proto_files: &[P]) -> error::Result<bool>
where
    P: AsRef<path::Path>,
{
    if cache.exists() {
        let cache_metadata = fs::metadata(&cache)?;
        let cache_mtime = cache_metadata.modified()?;
        let mut max_proto_mtime = std::time::SystemTime::UNIX_EPOCH;

        for proto_file in proto_files.iter() {
            let proto_metadata = fs::metadata(&proto_file)?;
            let proto_mtime = proto_metadata.modified()?;
            max_proto_mtime = cmp::max(max_proto_mtime, proto_mtime);
        }

        Ok(cache_mtime < max_proto_mtime)
    } else {
        Ok(true)
    }
}