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
34pub 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 if let Some(build) = load_cache()? {
45 return Ok(build);
46 }
47
48 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 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 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 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 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}