ALVR-IPv6/alvr/xtask/src/main.rs

549 lines
17 KiB
Rust

mod command;
mod dependencies;
mod version;
use fs_extra::{self as fsx, dir as dirx};
use pico_args::Arguments;
use std::{
env,
error::Error,
fs,
path::{Path, PathBuf},
};
const HELP_STR: &str = r#"
cargo xtask
Developement actions for ALVR.
USAGE:
cargo xtask <SUBCOMMAND> [FLAG] [ARGS]
SUBCOMMANDS:
build-windows-deps Download and compile external dependencies for Windows
build-android-deps Download and compile external dependencies for Android
build-server Build server driver, then copy binaries to build folder
build-client Build client, then copy binaries to build folder
build-ffmpeg-linux Build FFmpeg with VAAPI and Vulkan support. Only for CI
publish-server Build server in release mode, make portable version and installer
publish-client Build client for all headsets
clean Removes build folder
kill-oculus Kill all Oculus processes
bump-versions Bump server and client package versions
clippy Show warnings for selected clippy lints
FLAGS:
--fetch Update crates with "cargo update". Used only for build subcommands
--release Optimized build without debug info. Used only for build subcommands
--nightly Bump versions to nightly and build. Used only for publish subcommand
--oculus-quest Oculus Quest build. Used only for build-client subcommand
--oculus-go Oculus Go build. Used only for build-client subcommand
--help Print this text
ARGS:
--version <VERSION> Specify version to set with the bump-versions subcommand
"#;
type BResult<T = ()> = Result<T, Box<dyn Error>>;
#[cfg(target_os = "linux")]
const SERVER_BUILD_DIR_NAME: &str = "alvr_server_linux";
#[cfg(windows)]
const SERVER_BUILD_DIR_NAME: &str = "alvr_server_windows";
#[cfg(target_os = "linux")]
const STEAMVR_OS_DIR_NAME: &str = "linux64";
#[cfg(windows)]
const STEAMVR_OS_DIR_NAME: &str = "win64";
#[cfg(target_os = "linux")]
const DRIVER_FNAME: &str = "driver_alvr_server.so";
#[cfg(windows)]
const DRIVER_FNAME: &str = "driver_alvr_server.dll";
#[cfg(target_os = "linux")]
pub fn exec_fname(name: &str) -> String {
name.to_owned()
}
#[cfg(windows)]
pub fn exec_fname(name: &str) -> String {
format!("{}.exe", name)
}
#[cfg(target_os = "linux")]
fn dynlib_fname(name: &str) -> String {
format!("lib{}.so", name)
}
#[cfg(windows)]
fn dynlib_fname(name: &str) -> String {
format!("{}.dll", name)
}
pub fn target_dir() -> PathBuf {
Path::new(env!("OUT_DIR")).join("../../../..")
}
pub fn workspace_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.parent()
.unwrap()
.into()
}
pub fn build_dir() -> PathBuf {
workspace_dir().join("build")
}
pub fn server_build_dir() -> PathBuf {
build_dir().join(SERVER_BUILD_DIR_NAME)
}
pub fn remove_build_dir() {
let build_dir = build_dir();
fs::remove_dir_all(&build_dir).ok();
}
pub fn build_server(is_release: bool, is_nightly: bool, fetch_crates: bool, new_dashboard: bool) {
let build_type = if is_release { "release" } else { "debug" };
let build_flag = if is_release { "--release" } else { "" };
let target_dir = target_dir();
let artifacts_dir = target_dir.join(build_type);
let driver_dst_dir = server_build_dir().join("bin").join(STEAMVR_OS_DIR_NAME);
if fetch_crates {
command::run("cargo update").unwrap();
}
fs::remove_dir_all(&server_build_dir()).ok();
fs::create_dir_all(&server_build_dir()).unwrap();
// get all file and folder paths at depth 1, excluded template root (at index 0)
let dir_content = dirx::get_dir_content2(
workspace_dir()
.join("alvr")
.join("xtask")
.join("server_release_template"),
&dirx::DirOptions { depth: 1 },
)
.unwrap();
let items: Vec<&String> = dir_content.directories[1..]
.iter()
.chain(dir_content.files.iter())
.collect();
fsx::copy_items(&items, server_build_dir(), &dirx::CopyOptions::new()).unwrap();
fs::create_dir_all(&driver_dst_dir).unwrap();
if is_nightly {
command::run_in(
&workspace_dir().join("alvr/server"),
&format!("cargo build {} --features alvr_common/nightly", build_flag),
)
.unwrap();
command::run_in(
&workspace_dir().join("alvr/launcher"),
&format!("cargo build {} --features alvr_common/nightly", build_flag),
)
.unwrap();
} else if new_dashboard {
command::run_in(
&workspace_dir().join("alvr/server"),
&format!(
"cargo build --no-default-features {} {}",
" --features new_dashboard --features alvr_common/new_dashboard", build_flag
),
)
.unwrap();
command::run_in(
&workspace_dir().join("alvr/launcher"),
&format!(
"cargo build --no-default-features {} {}",
"--features alvr_common/new_dashboard", build_flag
),
)
.unwrap();
} else {
command::run(&format!(
"cargo build -p alvr_server -p alvr_launcher {}",
build_flag
))
.unwrap();
}
fs::copy(
artifacts_dir.join(dynlib_fname("alvr_server")),
driver_dst_dir.join(DRIVER_FNAME),
)
.unwrap();
if cfg!(windows) {
let dir_content = dirx::get_dir_content("alvr/server/cpp/bin/windows").unwrap();
fsx::copy_items(
&dir_content.files,
driver_dst_dir,
&dirx::CopyOptions::new(),
)
.unwrap();
}
if new_dashboard {
command::run_in(
&workspace_dir().join("alvr/dashboard"),
&format!(
"npm install && npx webpack --mode {} --output-path=../../build/{}/dashboard",
if is_release {
"production"
} else {
"development"
},
SERVER_BUILD_DIR_NAME,
),
)
.unwrap()
} else {
let dir_content =
dirx::get_dir_content2("alvr/legacy_dashboard", &dirx::DirOptions { depth: 1 })
.unwrap();
let items: Vec<&String> = dir_content.directories[1..]
.iter()
.chain(dir_content.files.iter())
.collect();
let destination = server_build_dir().join("dashboard");
fs::create_dir_all(&destination).unwrap();
fsx::copy_items(&items, destination, &dirx::CopyOptions::new()).unwrap();
}
if cfg!(target_os = "linux") {
command::run_in(
&workspace_dir().join("alvr/vulkan-layer"),
&format!("cargo build {}", build_flag),
)
.unwrap();
let lib_dir = server_build_dir().join("lib64");
let manifest_dir = server_build_dir().join("share/vulkan/explicit_layer.d");
fs::create_dir_all(&manifest_dir).unwrap();
fs::create_dir_all(&lib_dir).unwrap();
fs::copy(
workspace_dir().join("alvr/vulkan-layer/layer/alvr_x86_64.json"),
manifest_dir.join("alvr_x86_64.json"),
)
.unwrap();
fs::copy(
artifacts_dir.join(dynlib_fname("alvr_vulkan_layer")),
lib_dir.join(dynlib_fname("alvr_vulkan_layer")),
)
.unwrap();
}
fs::copy(
artifacts_dir.join(exec_fname("alvr_launcher")),
server_build_dir().join(exec_fname("ALVR Launcher")),
)
.unwrap();
}
pub fn build_client(is_release: bool, is_nightly: bool, for_oculus_go: bool, new_dashboard: bool) {
let headset_name = if for_oculus_go {
"oculus_go"
} else {
"oculus_quest"
};
let headset_type = if for_oculus_go {
"OculusGo"
} else {
"OculusQuest"
};
let package_type = if is_nightly { "Nightly" } else { "Stable" };
let build_type = &*format!(
"{}{}",
if is_release { "release" } else { "debug" },
if new_dashboard { "NewDashboard" } else { "" }
);
let build_task = format!("assemble{}{}{}", headset_type, package_type, build_type);
let client_dir = workspace_dir().join("alvr/client/android");
let command_name = if cfg!(not(windows)) {
"gradlew"
} else {
"gradlew.bat"
};
let artifact_name = format!("alvr_client_{}", headset_name);
fs::create_dir_all(&build_dir().join(&artifact_name)).unwrap();
env::set_current_dir(&client_dir).unwrap();
command::run(&format!("{} {}", command_name, build_task)).unwrap();
env::set_current_dir(workspace_dir()).unwrap();
fs::copy(
client_dir
.join("app/build/outputs/apk")
.join(format!("{}{}", headset_type, package_type))
.join(build_type)
.join(format!(
"app-{}-{}-{}.apk",
headset_type, package_type, build_type
)),
build_dir()
.join(&artifact_name)
.join(format!("{}.apk", artifact_name)),
)
.unwrap();
}
fn build_installer(wix_path: &str) {
let wix_path = PathBuf::from(wix_path).join("bin");
let heat_cmd = wix_path.join("heat.exe");
let candle_cmd = wix_path.join("candle.exe");
let light_cmd = wix_path.join("light.exe");
// Clear away build and prerelease version specifiers, MSI can have only dot-separated numbers.
let mut version = version::version();
if let Some(idx) = version.find('-') {
version = version[..idx].to_owned();
}
if let Some(idx) = version.find('+') {
version = version[..idx].to_owned();
}
command::run_without_shell(
&heat_cmd.to_string_lossy(),
&[
"dir",
"build\\alvr_server_windows",
"-ag",
"-sreg",
"-srd",
"-dr",
"APPLICATIONFOLDER",
"-cg",
"BuildFiles",
"-var",
"var.BuildRoot",
"-o",
"target\\wix\\harvested.wxs",
],
)
.unwrap();
command::run_without_shell(
&candle_cmd.to_string_lossy(),
&[
"-arch",
"x64",
"-dBuildRoot=build\\alvr_server_windows",
"-ext",
"WixUtilExtension",
&format!("-dVersion={}", version),
"alvr\\xtask\\wix\\main.wxs",
"target\\wix\\harvested.wxs",
"-o",
"target\\wix\\",
],
)
.unwrap();
command::run_without_shell(
&light_cmd.to_string_lossy(),
&[
"target\\wix\\main.wixobj",
"target\\wix\\harvested.wixobj",
"-ext",
"WixUIExtension",
"-ext",
"WixUtilExtension",
"-o",
"target\\wix\\alvr.msi",
],
)
.unwrap();
// Build the bundle including ALVR and vc_redist.
command::run_without_shell(
&candle_cmd.to_string_lossy(),
&[
"-arch",
"x64",
"-dBuildRoot=build\\alvr_server_windows",
"-ext",
"WixUtilExtension",
"-ext",
"WixBalExtension",
"alvr\\xtask\\wix\\bundle.wxs",
"-o",
"target\\wix\\",
],
)
.unwrap();
command::run_without_shell(
&light_cmd.to_string_lossy(),
&[
"target\\wix\\bundle.wixobj",
"-ext",
"WixUtilExtension",
"-ext",
"WixBalExtension",
"-o",
&format!("build\\ALVR_Installer_v{}.exe", version),
],
)
.unwrap();
}
pub fn publish_server(is_nightly: bool) {
build_server(true, is_nightly, false, false);
// Add licenses
let licenses_dir = server_build_dir().join("licenses");
fs::create_dir_all(&licenses_dir).unwrap();
fs::copy(
workspace_dir().join("LICENSE"),
licenses_dir.join("ALVR.txt"),
)
.unwrap();
command::run("cargo install cargo-about").unwrap();
command::run(&format!(
"cargo about generate {} > {}",
workspace_dir()
.join("alvr")
.join("xtask")
.join("licenses_template.hbs")
.to_string_lossy(),
licenses_dir.join("dependencies.html").to_string_lossy()
))
.unwrap();
fs::copy(
workspace_dir()
.join("alvr")
.join("server")
.join("LICENSE-Valve"),
licenses_dir.join("Valve.txt"),
)
.unwrap();
command::zip(&server_build_dir()).unwrap();
if cfg!(windows) {
if is_nightly {
fs::copy(
target_dir().join("release").join("alvr_server.pdb"),
build_dir().join("alvr_server.pdb"),
)
.unwrap();
}
if let Some(wix_evar) = env::vars().find(|v| v.0 == "WIX") {
println!("Found WiX, will build installer.");
build_installer(&wix_evar.1);
} else {
println!("No WiX toolset installation found, skipping installer.");
}
}
}
pub fn publish_client(is_nightly: bool) {
build_client(!is_nightly, is_nightly, false, false);
build_client(!is_nightly, is_nightly, true, false);
}
// Avoid Oculus link popups when debugging the client
pub fn kill_oculus_processes() {
command::run_without_shell(
"powershell",
&[
"Start-Process",
"taskkill",
"-ArgumentList",
"\"/F /IM OVR* /T\"",
"-Verb",
"runAs",
],
)
.unwrap();
}
fn clippy() {
command::run(&format!(
"cargo clippy {} -- {} {} {} {} {} {} {} {} {} {} {}",
"-p alvr_xtask -p alvr_common -p alvr_launcher -p alvr_dashboard", // todo: add more crates when they compile correctly
"-W clippy::clone_on_ref_ptr -W clippy::create_dir -W clippy::dbg_macro",
"-W clippy::decimal_literal_representation -W clippy::else_if_without_else",
"-W clippy::exit -W clippy::expect_used -W clippy::filetype_is_file",
"-W clippy::float_cmp_const -W clippy::get_unwrap -W clippy::let_underscore_must_use",
"-W clippy::lossy_float_literal -W clippy::map_err_ignore -W clippy::mem_forget",
"-W clippy::multiple_inherent_impl -W clippy::print_stderr -W clippy::print_stderr",
"-W clippy::rc_buffer -W clippy::rest_pat_in_fully_bound_structs -W clippy::str_to_string",
"-W clippy::string_to_string -W clippy::todo -W clippy::unimplemented",
"-W clippy::unneeded_field_pattern -W clippy::unwrap_in_result",
"-W clippy::verbose_file_reads -W clippy::wildcard_enum_match_arm",
"-W clippy::wrong_pub_self_convention"
))
.unwrap();
}
fn main() {
env::set_var("RUST_BACKTRACE", "1");
let mut args = Arguments::from_env();
if args.contains(["-h", "--help"]) {
println!("{}", HELP_STR);
} else if let Ok(Some(subcommand)) = args.subcommand() {
let fetch = args.contains("--fetch");
let is_release = args.contains("--release");
let version: Option<String> = args.opt_value_from_str("--version").unwrap();
let is_nightly = args.contains("--nightly");
let for_oculus_quest = args.contains("--oculus-quest");
let for_oculus_go = args.contains("--oculus-go");
let new_dashboard = args.contains("--new-dashboard");
if args.finish().is_empty() {
match subcommand.as_str() {
"build-windows-deps" => dependencies::build_deps("windows"),
"build-android-deps" => dependencies::build_deps("android"),
"build-server" => build_server(is_release, false, fetch, new_dashboard),
"build-client" => {
if (for_oculus_quest && for_oculus_go) || (!for_oculus_quest && !for_oculus_go)
{
build_client(is_release, false, false, new_dashboard);
build_client(is_release, false, true, new_dashboard);
} else {
build_client(is_release, false, for_oculus_go, new_dashboard);
}
}
"build-ffmpeg-linux" => dependencies::build_ffmpeg_linux(),
"publish-server" => publish_server(is_nightly),
"publish-client" => publish_client(is_nightly),
"clean" => remove_build_dir(),
"kill-oculus" => kill_oculus_processes(),
"bump-versions" => version::bump_version(version, is_nightly),
"clippy" => clippy(),
_ => {
println!("\nUnrecognized subcommand.");
println!("{}", HELP_STR);
return;
}
}
} else {
println!("\nWrong arguments.");
println!("{}", HELP_STR);
return;
}
} else {
println!("\nMissing subcommand.");
println!("{}", HELP_STR);
return;
}
println!("\nDone\n");
}