use crate::{Architecture, Platform};
use std::process::{Command, ExitStatus};
use std::{collections::BTreeMap, fmt::Display, os::windows::process::CommandExt, path::PathBuf};
use unicase::Ascii;
mod patcher;
pub use patcher::*;
mod generator;
pub use generator::*;
mod consts {
pub const EXT_GROUPPROJ: &'static str = "groupproj";
pub const EXT_DPROJ: &'static str = "dproj";
pub const EXT_CBPROJ: &'static str = "cbproj";
pub const PROPERTY_GROUP: &'static str = "PropertyGroup";
pub const PROPERTY_GROUP_BASE_CONDITION: &'static str = "'$(Base)'!=''";
pub const CONDITION: &'static str = "Condition";
pub const ITEM_GROUP: &'static str = "ItemGroup";
pub const PROJECTS: &'static str = "Projects";
pub const INCLUDE: &'static str = "Include";
pub const TARGET: &'static str = "Target";
pub const NAME: &'static str = "Name";
pub const MSBUILD: &'static str = "MSBuild";
pub const CALL_TARGET: &'static str = "CallTarget";
pub const TARGETS: &'static str = "Targets";
pub const BUILD: &'static str = "Build";
pub const CONFIG: &'static str = "Config";
pub const PLATFORM: &'static str = "Platform";
pub const DCC_USE_MSBUILD_EXTERNALLY: &'static str = "DCC_UseMSBuildExternally";
pub const DCC_PREFERRED_TOOL_ARCHITECTURE: &'static str = "DCC_PreferredToolArchitecture";
pub const VERINFO_INCLUDE_VERINFO: &'static str = "VerInfo_IncludeVerInfo";
pub const VERINFO_KEYS: &'static str = "VerInfo_Keys";
pub const PRE_BUILD_EVENT: &'static str = "PreBuildEvent";
pub const POST_BUILD_EVENT: &'static str = "PostBuildEvent";
}
use consts::*;
#[derive(Debug, Clone, clap::Args)]
pub struct Options {
pub file: PathBuf,
#[arg(short, long)]
pub config: Option<String>,
#[arg(short = 't', long, aliases = ["pta", "tool", "tools", "toolarch", "tool-arch"], value_name = "ARCH", ignore_case = true)]
pub preferred_tool_architecture: Option<Architecture>,
#[command(flatten)]
pub version_info: VersionInfo,
#[arg(skip)]
pub no_logo: bool,
}
#[derive(Debug, Clone, clap::Args, serde::Serialize)]
#[command(
rename_all = "PascalCase",
next_help_heading = "Include Version Information"
)]
#[serde(rename_all = "PascalCase")]
pub struct VersionInfo {
#[arg(long)]
company_name: Option<String>,
#[arg(long)]
file_description: Option<String>,
#[arg(long)]
file_version: Option<String>,
#[arg(long)]
internal_name: Option<String>,
#[arg(long)]
legal_copyright: Option<String>,
#[arg(long)]
legal_trademarks: Option<String>,
#[arg(long)]
original_filename: Option<String>,
#[arg(long)]
product_name: Option<String>,
#[arg(long)]
product_version: Option<String>,
#[arg(long)]
comments: Option<String>,
#[arg(long = "VersionInfo")]
#[serde(skip)]
raw: Option<String>,
}
impl Display for VersionInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut map = BTreeMap::new();
if let Ok(serde_json::Value::Object(obj)) = serde_json::to_value(self) {
for (k, v) in obj {
if let serde_json::Value::String(s) = v {
map.insert(Ascii::new(k), s);
}
}
};
if let Some(raw) = &self.raw {
for (k, v) in raw.split(';').filter_map(|s| s.split_once('=')) {
map.entry(Ascii::new(k.trim().to_owned()))
.or_insert(v.trim().to_owned());
}
}
for (i, (k, v)) in map.iter().enumerate() {
if i > 0 {
write!(f, ";")?;
}
write!(f, "{k}={v}")?;
}
Ok(())
}
}
pub trait Execute {
fn execute(&self, platform: &Option<Platform>, options: &Options) -> crate::Result<ExitStatus>;
}
pub struct MsBuild {
rsvars_bat: PathBuf,
}
impl MsBuild {
pub(crate) fn new(rsvars_bat: PathBuf) -> Self {
Self { rsvars_bat }
}
}
impl Execute for MsBuild {
fn execute(&self, platform: &Option<Platform>, options: &Options) -> crate::Result<ExitStatus> {
let mut args = vec![
format!("\"{}\"", options.file.display()),
"/t:Build".to_string(),
];
if let Some(config) = &options.config {
args.push(format!("/p:{CONFIG}={config}"));
}
if let Some(platform) = platform {
args.push(format!("/p:{PLATFORM}={platform}"));
}
if let Some(arch) = &options.preferred_tool_architecture {
args.push(format!("/p:{DCC_PREFERRED_TOOL_ARCHITECTURE}={arch}"));
}
let version_info = options.version_info.to_string();
if !version_info.is_empty() {
args.push(format!("/p:{VERINFO_INCLUDE_VERINFO}=true"));
args.push(format!("/p:{VERINFO_KEYS}=\"{version_info}\""));
}
if options.no_logo {
args.push("/nologo".to_string());
}
let cmd_arg = format!(
"\"{}\" && MSBuild.exe {}",
self.rsvars_bat.display(),
args.join(" ")
);
let status = Command::new("cmd.exe")
.arg("/C")
.raw_arg(format!("\" {cmd_arg} \""))
.status()?;
Ok(status)
}
}