use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Compiler {
Csc,
DotNet,
Mcs,
}
impl Compiler {
pub fn command(&self) -> &'static str {
match self {
Compiler::Csc => "csc",
Compiler::DotNet => "dotnet",
Compiler::Mcs => "mcs",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Runtime {
Mono,
DotNet,
}
impl Runtime {
pub fn command(&self) -> &'static str {
match self {
Runtime::Mono => "mono",
Runtime::DotNet => "dotnet",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Architecture {
pub name: &'static str,
pub csc_flag: Option<&'static str>,
pub dotnet_platform: Option<&'static str>,
}
impl Architecture {
pub const X86: Self = Self {
name: "x86",
csc_flag: Some("/platform:x86"),
dotnet_platform: Some("x86"),
};
pub const X64: Self = Self {
name: "x64",
csc_flag: Some("/platform:x64"),
dotnet_platform: Some("x64"),
};
pub const ANYCPU: Self = Self {
name: "anycpu",
csc_flag: None,
dotnet_platform: None,
};
pub const ARM64: Self = Self {
name: "arm64",
csc_flag: Some("/platform:arm64"),
dotnet_platform: Some("ARM64"),
};
pub fn filename_suffix(&self) -> &str {
self.name
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Disassembler {
Monodis,
Ildasm,
DotNetIldasm,
}
impl Disassembler {
pub fn command(&self) -> &'static str {
match self {
Disassembler::Monodis => "monodis",
Disassembler::Ildasm => "ildasm",
Disassembler::DotNetIldasm => "dotnet-ildasm",
}
}
}
#[derive(Debug)]
pub struct TestCapabilities {
pub compiler: Option<Compiler>,
pub runtime: Option<Runtime>,
pub disassembler: Option<Disassembler>,
pub ildasm_path: Option<std::path::PathBuf>,
pub supported_architectures: Vec<Architecture>,
pub available_compilers: Vec<Compiler>,
pub available_runtimes: Vec<Runtime>,
pub available_disassemblers: Vec<Disassembler>,
}
impl TestCapabilities {
pub fn detect() -> Self {
let available_compilers = Self::detect_compilers();
let available_runtimes = Self::detect_runtimes();
let (available_disassemblers, ildasm_path) = Self::detect_disassemblers();
let (compiler, runtime) =
Self::select_compatible_compiler_runtime(&available_compilers, &available_runtimes);
let disassembler = if available_disassemblers.contains(&Disassembler::Monodis) {
Some(Disassembler::Monodis)
} else if available_disassemblers.contains(&Disassembler::Ildasm) {
Some(Disassembler::Ildasm)
} else if available_disassemblers.contains(&Disassembler::DotNetIldasm) {
Some(Disassembler::DotNetIldasm)
} else {
None
};
let supported_architectures = Self::determine_supported_architectures(compiler, runtime);
Self {
compiler,
runtime,
disassembler,
ildasm_path,
supported_architectures,
available_compilers,
available_runtimes,
available_disassemblers,
}
}
fn select_compatible_compiler_runtime(
compilers: &[Compiler],
runtimes: &[Runtime],
) -> (Option<Compiler>, Option<Runtime>) {
let has_mono = runtimes.contains(&Runtime::Mono);
let has_dotnet_runtime = runtimes.contains(&Runtime::DotNet);
let has_mcs = compilers.contains(&Compiler::Mcs);
let has_csc = compilers.contains(&Compiler::Csc);
let has_dotnet_compiler = compilers.contains(&Compiler::DotNet);
if has_mcs && has_mono {
return (Some(Compiler::Mcs), Some(Runtime::Mono));
}
if has_csc && has_mono {
return (Some(Compiler::Csc), Some(Runtime::Mono));
}
if has_mcs && has_dotnet_runtime {
return (Some(Compiler::Mcs), Some(Runtime::DotNet));
}
if has_csc && has_dotnet_runtime {
return (Some(Compiler::Csc), Some(Runtime::DotNet));
}
if has_dotnet_compiler && has_dotnet_runtime {
return (Some(Compiler::DotNet), Some(Runtime::DotNet));
}
(None, None)
}
pub fn can_test(&self) -> bool {
self.compiler.is_some()
&& self.runtime.is_some()
&& !self.supported_architectures.is_empty()
}
pub fn can_disassemble(&self) -> bool {
self.disassembler.is_some()
}
pub fn summary(&self) -> String {
let compiler_str = self.compiler.map(|c| c.command()).unwrap_or("none");
let runtime_str = self.runtime.map(|r| r.command()).unwrap_or("none");
let disasm_str = self.disassembler.map(|d| d.command()).unwrap_or("none");
let archs: Vec<&str> = self
.supported_architectures
.iter()
.map(|a| a.name)
.collect();
format!(
"Compiler: {}, Runtime: {}, Disassembler: {}, Architectures: [{}]",
compiler_str,
runtime_str,
disasm_str,
archs.join(", ")
)
}
fn detect_compilers() -> Vec<Compiler> {
let mut compilers = Vec::new();
if Self::command_exists("csc", &["/help"]) {
compilers.push(Compiler::Csc);
}
if Self::command_exists("mcs", &["--version"]) {
compilers.push(Compiler::Mcs);
}
if Self::command_exists("dotnet", &["--version"]) {
compilers.push(Compiler::DotNet);
}
compilers
}
fn detect_runtimes() -> Vec<Runtime> {
let mut runtimes = Vec::new();
if Self::command_exists("mono", &["--version"]) {
runtimes.push(Runtime::Mono);
}
if Self::command_exists("dotnet", &["--version"]) {
runtimes.push(Runtime::DotNet);
}
runtimes
}
fn detect_disassemblers() -> (Vec<Disassembler>, Option<std::path::PathBuf>) {
let mut disassemblers = Vec::new();
let mut ildasm_path = None;
if Self::command_exists("monodis", &["--help"]) {
disassemblers.push(Disassembler::Monodis);
}
if Self::command_exists("ildasm", &["/?"]) {
disassemblers.push(Disassembler::Ildasm);
} else if let Some(path) = Self::find_windows_sdk_ildasm() {
disassemblers.push(Disassembler::Ildasm);
ildasm_path = Some(path);
}
if !disassemblers.contains(&Disassembler::Ildasm)
&& Self::command_exists("dotnet-ildasm", &["--help"])
{
disassemblers.push(Disassembler::DotNetIldasm);
}
(disassemblers, ildasm_path)
}
fn find_windows_sdk_ildasm() -> Option<PathBuf> {
if !cfg!(target_os = "windows") {
return None;
}
let sdk_paths = [
r"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8.1 Tools",
r"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools",
r"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.7.2 Tools",
r"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.7.1 Tools",
r"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.7 Tools",
r"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.2 Tools",
r"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools",
r"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6 Tools",
];
for sdk_path in &sdk_paths {
let ildasm_exe = PathBuf::from(sdk_path).join("ildasm.exe");
if ildasm_exe.exists() && Self::command_at_path_exists(&ildasm_exe, &["/?"]) {
return Some(ildasm_exe);
}
}
None
}
fn command_at_path_exists(path: &Path, args: &[&str]) -> bool {
match Command::new(path)
.args(args)
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
{
Ok(status) => status.success(),
Err(_) => false,
}
}
fn command_exists(cmd: &str, args: &[&str]) -> bool {
Command::new(cmd)
.args(args)
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.is_ok()
}
fn determine_supported_architectures(
compiler: Option<Compiler>,
runtime: Option<Runtime>,
) -> Vec<Architecture> {
let (compiler, runtime) = match (compiler, runtime) {
(Some(c), Some(r)) => (c, r),
_ => return Vec::new(),
};
match (compiler, runtime) {
(Compiler::Mcs, Runtime::Mono) => {
#[cfg(target_arch = "x86_64")]
{
vec![Architecture::ANYCPU, Architecture::X64, Architecture::X86]
}
#[cfg(target_arch = "aarch64")]
{
vec![Architecture::ANYCPU]
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
vec![Architecture::ANYCPU]
}
}
(Compiler::Csc, Runtime::Mono) => {
#[cfg(target_arch = "x86_64")]
{
vec![Architecture::ANYCPU, Architecture::X64, Architecture::X86]
}
#[cfg(target_arch = "aarch64")]
{
vec![Architecture::ANYCPU, Architecture::ARM64]
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
vec![Architecture::ANYCPU]
}
}
(Compiler::DotNet, Runtime::DotNet) => {
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
{
vec![Architecture::ANYCPU, Architecture::X64]
}
#[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))]
{
vec![Architecture::ANYCPU, Architecture::X64]
}
#[cfg(target_arch = "aarch64")]
{
vec![Architecture::ANYCPU, Architecture::ARM64]
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
vec![Architecture::ANYCPU]
}
}
(Compiler::Mcs, Runtime::DotNet) => {
#[cfg(target_arch = "x86_64")]
{
vec![Architecture::ANYCPU, Architecture::X64]
}
#[cfg(target_arch = "aarch64")]
{
vec![Architecture::ANYCPU]
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
vec![Architecture::ANYCPU]
}
}
(Compiler::Csc, Runtime::DotNet) => {
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
{
vec![Architecture::ANYCPU, Architecture::X64]
}
#[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))]
{
vec![Architecture::ANYCPU, Architecture::X64]
}
#[cfg(target_arch = "aarch64")]
{
vec![Architecture::ANYCPU, Architecture::ARM64]
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
vec![Architecture::ANYCPU]
}
}
(Compiler::DotNet, Runtime::Mono) => {
Vec::new()
}
}
}
}
pub fn execute_assembly(
runtime: Runtime,
assembly_path: &Path,
) -> std::io::Result<std::process::Output> {
match runtime {
Runtime::Mono => Command::new("mono").arg(assembly_path).output(),
Runtime::DotNet => {
let mut cmd = Command::new("dotnet");
if let Some(parent) = assembly_path.parent() {
cmd.current_dir(parent);
if let Some(filename) = assembly_path.file_name() {
cmd.arg(filename);
} else {
cmd.arg(assembly_path);
}
} else {
cmd.arg(assembly_path);
}
cmd.output()
}
}
}
pub fn disassemble_assembly(
disassembler: Disassembler,
assembly_path: &Path,
) -> std::io::Result<std::process::Output> {
match disassembler {
Disassembler::Monodis => Command::new("monodis").arg(assembly_path).output(),
Disassembler::Ildasm => Command::new("ildasm")
.arg("/text")
.arg(assembly_path)
.output(),
Disassembler::DotNetIldasm => Command::new("dotnet-ildasm").arg(assembly_path).output(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_capabilities() {
let caps = TestCapabilities::detect();
println!("Detected: {}", caps.summary());
}
#[test]
fn test_architecture_constants() {
assert_eq!(Architecture::X86.name, "x86");
assert_eq!(Architecture::X64.name, "x64");
assert_eq!(Architecture::ANYCPU.name, "anycpu");
assert_eq!(Architecture::ARM64.name, "arm64");
assert_eq!(Architecture::X86.csc_flag, Some("/platform:x86"));
assert_eq!(Architecture::ANYCPU.csc_flag, None);
}
#[test]
fn test_compiler_commands() {
assert_eq!(Compiler::Csc.command(), "csc");
assert_eq!(Compiler::DotNet.command(), "dotnet");
assert_eq!(Compiler::Mcs.command(), "mcs");
}
#[test]
fn test_runtime_commands() {
assert_eq!(Runtime::Mono.command(), "mono");
assert_eq!(Runtime::DotNet.command(), "dotnet");
}
}