carla_bin/
lib.rs

1use anyhow::{ensure, Context, Result};
2use carla_src::{libcarla_client, probe};
3use once_cell::sync::{Lazy, OnceCell};
4use serde::Deserialize;
5use std::{
6    fs::{self, OpenOptions},
7    io::{self, BufReader},
8    path::{Path, PathBuf},
9};
10use tar::Archive;
11use xz::bufread::XzDecoder;
12
13pub struct CarlaBuild {
14    pub include_dirs: Vec<PathBuf>,
15    pub lib_dirs: Vec<PathBuf>,
16}
17
18#[derive(Deserialize)]
19struct PrebuildConfig {
20    pub url: String,
21    pub dir_name: PathBuf,
22}
23
24const OUT_DIR: &str = env!("OUT_DIR");
25const TAG: &str = include_str!(concat!(env!("OUT_DIR"), "/TAG"));
26const BUILD_READY_FILE: &str = concat!(env!("OUT_DIR"), "/BUILD_READY");
27const DOWNLOAD_READY_FILE: &str = concat!(env!("OUT_DIR"), "/DOWNLOAD_READY");
28
29static PREBUILD_CONFIG: Lazy<PrebuildConfig> = Lazy::new(|| {
30    let text = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/prebuild.json5"));
31    json5::from_str(text).unwrap()
32});
33
34/// Prepares the Carla simulator source code and build client library
35/// when called.
36pub fn build_carla() -> Result<&'static CarlaBuild> {
37    static ONCE: OnceCell<CarlaBuild> = OnceCell::new();
38    let build = ONCE.get_or_try_init(cached_or_build)?;
39    Ok(build)
40}
41
42fn cached_or_build() -> Result<CarlaBuild> {
43    // Try to use cached files
44    if let Some(build) = load_cache()? {
45        return Ok(build);
46    }
47
48    // Download source code
49    let src_dir = carla_src::Download::default()
50        .run()
51        .with_context(|| "Failed to prepare LibCarla.client C++ library")?;
52    let probe = probe(&src_dir);
53
54    // Build if it wasn't done before
55    let build_ready_file = Path::new(BUILD_READY_FILE);
56    skip_or_run(build_ready_file, || {
57        libcarla_client::build(&src_dir)?;
58        anyhow::Ok(())
59    })?;
60
61    Ok(CarlaBuild {
62        include_dirs: probe.include_dirs.into_vec(),
63        lib_dirs: probe.lib_dirs.into_vec(),
64    })
65}
66
67fn load_cache() -> Result<Option<CarlaBuild>> {
68    cached_or_download()?;
69    let install_dir = install_dir();
70
71    if !install_dir.is_dir() {
72        return Ok(None);
73    }
74
75    let include_dir = install_dir.join("include");
76    let lib_dir = install_dir.join("lib");
77
78    Ok(Some(CarlaBuild {
79        include_dirs: vec![include_dir],
80        lib_dirs: vec![lib_dir],
81    }))
82}
83
84fn cached_or_download() -> Result<()> {
85    let download_ready_file = Path::new(DOWNLOAD_READY_FILE);
86    skip_or_run(download_ready_file, download_prebuild)?;
87    Ok(())
88}
89
90fn download_prebuild() -> Result<()> {
91    let prebuild_dir = prebuild_dir();
92
93    // Clean the dir to extract to
94    let result = fs::remove_dir_all(&prebuild_dir);
95    match result {
96        Ok(()) => {}
97        Err(err) if err.kind() == io::ErrorKind::NotFound => {}
98        Err(err) => return Err(err.into()),
99    }
100
101    // Download and extract to prebuild_dir
102    let PrebuildConfig { url, .. } = &*PREBUILD_CONFIG;
103
104    let reader = ureq::get(url)
105        .call()
106        .with_context(|| format!("Failed to download from URL '{}'", url))?
107        .into_reader();
108    let reader = BufReader::new(reader);
109    let mut archive = Archive::new(XzDecoder::new(reader));
110    archive
111        .unpack(OUT_DIR)
112        .with_context(|| format!("Failed to unpack file downloaded from '{}'", url))?;
113
114    // Check if desired extracted files exist
115    ensure!(
116        prebuild_dir.is_dir(),
117        "'{}' does not exist. Is the URL '{}' correct?",
118        prebuild_dir.display(),
119        url
120    );
121
122    Ok(())
123}
124
125fn skip_or_run<T, F>(target_path: &Path, callback: F) -> Result<Option<T>>
126where
127    F: FnOnce() -> Result<T>,
128{
129    if target_path.exists() {
130        return Ok(None);
131    }
132    let output = (callback)()?;
133    touch(target_path)?;
134    Ok(Some(output))
135}
136
137fn touch(path: &Path) -> Result<()> {
138    OpenOptions::new().create(true).write(true).open(path)?;
139    Ok(())
140}
141
142fn prebuild_dir() -> PathBuf {
143    Path::new(OUT_DIR).join(&PREBUILD_CONFIG.dir_name)
144}
145
146fn install_dir() -> PathBuf {
147    prebuild_dir().join(TAG)
148}