use crate::{Probe, probe};
use anyhow::{Context, Result, bail};
use fs_extra::dir::CopyOptions;
use std::{fs, path::Path, process::Command};
pub fn version() -> &'static str {
option_env!("CARLA_VERSION").unwrap_or("0.10.0")
}
pub fn libs() -> &'static [&'static str] {
let v = std::env::var("CARLA_VERSION").unwrap_or_else(|_| version().to_string());
match v.as_str() {
"0.10.0" => &[
"static=carla-client",
"static=Recast",
"static=Detour",
"static=DetourCrowd",
"static=rpc",
"static=png16",
"static=z",
"static=boost_filesystem",
],
_ => &[
"static=carla_client",
"static=Recast",
"static=Detour",
"static=DetourCrowd",
"static=rpc",
"static=png",
"static=boost_filesystem",
],
}
}
pub fn build<P>(src_dir: P) -> Result<()>
where
P: AsRef<Path>,
{
let carla_version = std::env::var("CARLA_VERSION").unwrap_or_else(|_| version().to_string());
match carla_version.as_str() {
"0.9.14" => build_0914(src_dir),
"0.9.15" => build_0915(src_dir),
"0.9.16" => build_0916(src_dir),
"0.10.0" => build_0_10_0(src_dir),
_ => bail!(
"Unsupported CARLA version: {}. Supported versions: 0.9.14, 0.9.15, 0.9.16, 0.10.0",
carla_version
),
}
}
fn pin_carla_version_branch(src_dir: &Path, version: &str) -> Result<()> {
let branch = format!("ue4/{version}");
let status = Command::new("git")
.args(["checkout", "-B", &branch])
.current_dir(src_dir)
.status()
.with_context(|| format!("failed to run `git checkout -B {branch}`"))?;
if !status.success() {
bail!("`git checkout -B {branch}` failed in {}", src_dir.display());
}
Ok(())
}
fn build_0914<P>(src_dir: P) -> Result<()>
where
P: AsRef<Path>,
{
let src_dir = src_dir.as_ref();
pin_carla_version_branch(src_dir, "0.9.14")?;
let err = || {
format!(
"`make LibCarla.client.release` failed in {}",
src_dir.display()
)
};
let status = Command::new("make")
.arg("LibCarla.client.release")
.current_dir(src_dir)
.status()
.with_context(err)?;
if !status.success() {
bail!("{}", err());
}
Ok(())
}
fn build_0915<P>(src_dir: P) -> Result<()>
where
P: AsRef<Path>,
{
let src_dir = src_dir.as_ref();
pin_carla_version_branch(src_dir, "0.9.15")?;
let ue4_root = src_dir.join("Unreal").join("UnrealEngine");
unsafe { std::env::set_var("UE4_ROOT", &ue4_root) };
if !ue4_root.join("Engine").exists() {
eprintln!("Cloning UnrealEngine 4.26 (shallow clone to save space)...");
eprintln!("Note: We only need the toolchain, not the full build");
if ue4_root.exists() {
fs::remove_dir_all(&ue4_root)?;
}
fs::create_dir_all(ue4_root.parent().unwrap())?;
let status = Command::new("git")
.args([
"clone",
"--depth",
"1",
"-b",
"carla",
"https://github.com/CarlaUnreal/UnrealEngine.git",
ue4_root.to_str().unwrap(),
])
.status()
.context("Failed to clone UnrealEngine")?;
if !status.success() {
bail!("Failed to clone UnrealEngine");
}
}
if !ue4_root.join("Build/OneTimeSetupPerformed").exists() {
eprintln!("Downloading UE4 toolchain (~734 MB)...");
eprintln!("Note: We skip the full UnrealEngine build to save ~91GB");
let git_file = ue4_root.join(".git");
let git_tmp = ue4_root.join(".git.tmp");
let git_file_renamed = git_file.is_file() && !git_file.is_dir();
if git_file_renamed {
eprintln!("Note: Detected .git file reference - temporarily renaming for Setup.sh");
fs::rename(&git_file, &git_tmp)?;
}
let status = Command::new("./Setup.sh")
.current_dir(&ue4_root)
.status()
.context("Failed to run UnrealEngine Setup.sh")?;
if git_file_renamed {
fs::rename(&git_tmp, &git_file)?;
eprintln!("Restored .git file reference");
}
if !status.success() {
bail!("UnrealEngine Setup.sh failed");
}
eprintln!("Toolchain downloaded successfully!");
}
eprintln!("Running CARLA setup...");
let status = Command::new("make")
.arg("setup")
.current_dir(src_dir)
.env("UE4_ROOT", &ue4_root)
.status()
.context("Failed to run `make setup`")?;
if !status.success() {
bail!("`make setup` failed in {}", src_dir.display());
}
eprintln!("CARLA setup complete!");
eprintln!("Building LibCarla Client...");
let status = Command::new("make")
.arg("LibCarla.client.release")
.current_dir(src_dir)
.env("UE4_ROOT", &ue4_root)
.status()
.context("Failed to run `make LibCarla.client.release`")?;
if !status.success() {
bail!(
"`make LibCarla.client.release` failed in {}",
src_dir.display()
);
}
eprintln!("Build complete!");
eprintln!("Output locations:");
eprintln!(" Build folder: Build/libcarla-client-build.release");
eprintln!(" Install folder: PythonAPI/carla/dependencies");
Ok(())
}
fn build_0916<P>(src_dir: P) -> Result<()>
where
P: AsRef<Path>,
{
let src_dir = src_dir.as_ref();
pin_carla_version_branch(src_dir, "0.9.16")?;
let ue4_root = src_dir.join("Unreal").join("UnrealEngine");
unsafe { std::env::set_var("UE4_ROOT", &ue4_root) };
if !ue4_root.join("Engine").exists() {
eprintln!("Cloning UnrealEngine 4.26 (shallow clone to save space)...");
eprintln!("Note: We only need the toolchain, not the full build");
if ue4_root.exists() {
fs::remove_dir_all(&ue4_root)?;
}
fs::create_dir_all(ue4_root.parent().unwrap())?;
let status = Command::new("git")
.args([
"clone",
"--depth",
"1",
"-b",
"carla",
"https://github.com/CarlaUnreal/UnrealEngine.git",
ue4_root.to_str().unwrap(),
])
.status()
.context("Failed to clone UnrealEngine")?;
if !status.success() {
bail!("Failed to clone UnrealEngine");
}
}
if !ue4_root.join("Build/OneTimeSetupPerformed").exists() {
eprintln!("Downloading UE4 toolchain (~734 MB)...");
eprintln!("Note: We skip the full UnrealEngine build to save ~91GB");
let git_file = ue4_root.join(".git");
let git_tmp = ue4_root.join(".git.tmp");
let git_file_renamed = git_file.is_file() && !git_file.is_dir();
if git_file_renamed {
eprintln!("Note: Detected .git file reference - temporarily renaming for Setup.sh");
fs::rename(&git_file, &git_tmp)?;
}
let status = Command::new("./Setup.sh")
.current_dir(&ue4_root)
.status()
.context("Failed to run UnrealEngine Setup.sh")?;
if git_file_renamed {
fs::rename(&git_tmp, &git_file)?;
eprintln!("Restored .git file reference");
}
if !status.success() {
bail!("UnrealEngine Setup.sh failed");
}
eprintln!("Toolchain downloaded successfully!");
}
eprintln!("Running CARLA setup...");
let status = Command::new("make")
.arg("setup")
.current_dir(src_dir)
.env("UE4_ROOT", &ue4_root)
.status()
.context("Failed to run `make setup`")?;
if !status.success() {
bail!("`make setup` failed in {}", src_dir.display());
}
eprintln!("CARLA setup complete!");
eprintln!("Building LibCarla Client...");
let status = Command::new("make")
.arg("LibCarla.client.release")
.current_dir(src_dir)
.env("UE4_ROOT", &ue4_root)
.status()
.context("Failed to run `make LibCarla.client.release`")?;
if !status.success() {
bail!(
"`make LibCarla.client.release` failed in {}",
src_dir.display()
);
}
eprintln!("Build complete!");
eprintln!("Output locations:");
eprintln!(" Build folder: Build/libcarla-client-build.release");
eprintln!(" Install folder: PythonAPI/carla/dependencies");
Ok(())
}
fn build_0_10_0<P>(src_dir: P) -> Result<()>
where
P: AsRef<Path>,
{
let src_dir = src_dir.as_ref();
let build_dir = src_dir.join("Build/libstdcxx-release");
eprintln!("Configuring CARLA 0.10.0 with CMake...");
let status = Command::new("cmake")
.args([
"-B",
build_dir.to_str().unwrap(),
"-G",
"Ninja",
"-DCMAKE_BUILD_TYPE=Release",
"-DBUILD_CARLA_CLIENT=ON",
"-DBUILD_CARLA_SERVER=OFF",
"-DBUILD_EXAMPLES=OFF",
"-DBUILD_LIBCARLA_TESTS=OFF",
"-DBUILD_PYTHON_API=OFF",
"-DBUILD_CARLA_UNREAL=OFF",
"-DENABLE_ROS2=OFF",
"-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
])
.current_dir(src_dir)
.status()
.with_context(|| format!("cmake configure failed in {}", src_dir.display()))?;
if !status.success() {
bail!("cmake configure failed in {}", src_dir.display());
}
eprintln!("Building CARLA 0.10.0 client library...");
let nproc = std::thread::available_parallelism()
.map(|n| n.get().to_string())
.unwrap_or_else(|_| "4".to_string());
let status = Command::new("cmake")
.args([
"--build",
build_dir.to_str().unwrap(),
"--target",
"carla-client",
"-j",
&nproc,
])
.current_dir(src_dir)
.status()
.with_context(|| format!("cmake build failed in {}", src_dir.display()))?;
if !status.success() {
bail!("cmake build failed in {}", src_dir.display());
}
eprintln!("Build complete!");
eprintln!("Output: {}/LibCarla/libcarla-client.a", build_dir.display());
Ok(())
}
pub fn install<S, T>(src_dir: S, tgt_dir: T) -> Result<()>
where
S: AsRef<Path>,
T: AsRef<Path>,
{
let tgt_dir = tgt_dir.as_ref();
let Probe {
include_dirs,
lib_dirs,
..
} = probe(src_dir)?;
{
let tgt_include_dir = tgt_dir.join("include");
fs::create_dir_all(&tgt_include_dir)?;
for dir in include_dirs.into_vec() {
fs_extra::dir::copy(
&dir,
&tgt_include_dir,
&CopyOptions {
overwrite: true,
skip_exist: false,
copy_inside: true,
content_only: true,
..Default::default()
},
)
.with_context(|| {
format!(
"Unable to copy directory from\
'{}'\
to\
'{}'",
dir.display(),
tgt_include_dir.display()
)
})?;
}
}
{
let tgt_lib_dir = tgt_dir.join("lib");
fs::create_dir_all(&tgt_lib_dir)?;
for dir in lib_dirs.into_vec() {
fs_extra::dir::copy(
&dir,
&tgt_lib_dir,
&CopyOptions {
overwrite: true,
skip_exist: false,
copy_inside: true,
content_only: true,
..Default::default()
},
)
.with_context(|| {
format!(
"Unable to copy directory from\
'{}'\
to\
'{}'",
dir.display(),
tgt_lib_dir.display()
)
})?;
}
}
Ok(())
}
pub fn clean<P>(src_dir: P) -> Result<()>
where
P: AsRef<Path>,
{
let src_dir = src_dir.as_ref();
let build_dir = src_dir.join("Build");
let carla_version = std::env::var("CARLA_VERSION").unwrap_or_else(|_| version().to_string());
if build_dir.exists() {
fs::remove_dir_all(&build_dir)?;
}
if carla_version == "0.10.0" {
return Ok(());
}
let err = || format!("`make clean.LibCarla` failed in {}", src_dir.display());
let status = Command::new("make")
.arg("clean.LibCarla")
.current_dir(src_dir)
.status()
.with_context(err)?;
if !status.success() {
bail!("{}", err());
}
Ok(())
}