dokan-sys 0.3.1+dokan206

Raw FFI bindings for Dokan (user mode file system library for Windows)
extern crate cc;

use std::{
	env, fs,
	process::{Command, Stdio},
};

use cc::{Build, Tool};

fn print_env(compiler: &Tool) {
	eprintln!("Environment variables:");
	for (k, v) in env::vars() {
		eprintln!("{}={}", k, v);
	}
	eprintln!("\nCompiler:\n{}", compiler.path().to_string_lossy());
	eprintln!("\nCompiler arguments:");
	for arg in compiler.args().iter() {
		eprintln!("{}", arg.to_string_lossy());
	}
	eprintln!("\nCompiler environment variables:");
	for (k, v) in compiler.env().iter() {
		eprintln!("{}={}", k.to_string_lossy(), v.to_string_lossy());
	}
}

fn run_generator(compiler: &Tool) -> String {
	let out_dir = env::var("OUT_DIR").unwrap();
	let mut compiler_cmd = compiler.to_command();
	compiler_cmd
		.stdout(Stdio::inherit())
		.stderr(Stdio::inherit())
		.arg("-Isrc/dokany/dokan")
		.arg("-Isrc/dokany/sys");
	if compiler.is_like_msvc() {
		compiler_cmd
			.arg(format!("/Fo{}/", out_dir))
			.arg("src/generate_version.c")
			.arg("/link")
			.arg(format!("/OUT:{}/generate_version.exe", out_dir))
	} else {
		compiler_cmd
			.arg(format!("-o{}/generate_version.exe", out_dir))
			.arg("src/generate_version.c")
	};
	assert!(compiler_cmd.output().unwrap().status.success());
	let generate_output = Command::new(format!("{}/generate_version.exe", out_dir))
		.current_dir(&out_dir)
		.output()
		.unwrap();
	assert!(generate_output.status.success());
	println!("cargo:rerun-if-changed=src/generate_version.c");

	String::from_utf8(fs::read(format!("{}/version.txt", out_dir)).unwrap()).unwrap()
}

fn check_dokan_env(version_major: &str) -> bool {
	let arch = match env::var("CARGO_CFG_TARGET_ARCH").unwrap().as_ref() {
		"x86" => "x86",
		"x86_64" => "x64",
		_ => panic!("Unsupported target architecture!"),
	};
	let env_name = format!("DokanLibrary{}_LibraryPath_{}", version_major, arch);
	println!("cargo:rerun-if-env-changed={}", env_name);
	if let Ok(lib_path) = env::var(&env_name) {
		println!("cargo:rustc-link-search=native={}", lib_path);
		true
	} else {
		println!(
			"cargo:warning=Environment variable {} not found, building Dokan from source.",
			env_name
		);
		false
	}
}

fn build_dokan(compiler: &Tool, version_major: &str) {
	let out_dir = env::var("OUT_DIR").unwrap();
	let src = fs::read_dir("src/dokany/dokan")
		.unwrap()
		.map(|d| d.unwrap().path())
		.filter(|p| {
			if let Some(ext) = p.extension() {
				ext == "c"
			} else {
				false
			}
		});
	let dll_name = format!("dokan{}.dll", version_major);
	let dll_path = format!("{}/{}", out_dir, dll_name);
	let mut compiler_cmd = compiler.to_command();
	compiler_cmd
		.stdout(Stdio::inherit())
		.stderr(Stdio::inherit())
		.arg("-D_WINDLL")
		.arg("-D_EXPORTING")
		.arg("-DUNICODE")
		.arg("-D_UNICODE")
		.arg("-DWINVER=0x0A00")
		.arg("-D_WIN32_WINNT=0x0A00")
		.arg("-Isrc/dokany/sys");
	if compiler.is_like_msvc() {
		compiler_cmd
			.arg(format!("/Fo{}/", out_dir))
			.args(src)
			.arg("/link")
			.arg("/DLL")
			.arg("/DEF:src/dokany/dokan/dokan.def")
			.arg(format!("/OUT:{}", dll_path))
			.arg(format!("/IMPLIB:{}/dokan{}.lib", out_dir, version_major))
			.arg("advapi32.lib")
			.arg("shell32.lib")
			.arg("user32.lib")
	} else {
		compiler_cmd
			.arg("-shared")
			.arg(format!("-o{}", dll_path))
			.args(src)
			.arg(format!(
				"-Wl,--out-implib,{}/dokan{}.lib",
				out_dir, version_major
			))
	};
	assert!(compiler_cmd.output().unwrap().status.success());
	if let Ok(output_path) = env::var("DOKAN_DLL_OUTPUT_PATH") {
		fs::copy(dll_path, format!("{}/{}", output_path, dll_name)).unwrap();
	}
	println!("cargo:rerun-if-env-changed=DOKAN_DLL_OUTPUT_PATH");
	println!("cargo:rustc-link-search=native={}", out_dir);
	println!("cargo:rerun-if-changed=src/dokany");
}

fn main() {
	let compiler = Build::new().get_compiler();
	print_env(&compiler);
	let version = run_generator(&compiler);
	assert_eq!(
		format!("dokan{}", version),
		env::var("CARGO_PKG_VERSION")
			.unwrap()
			.split('+')
			.last()
			.unwrap(),
		"Mismatch detected between crate version and bundled Dokan source version.",
	);
	let version_major = &version[..1];
	println!("cargo:rustc-link-lib=dylib=dokan{}", version_major);
	if !check_dokan_env(version_major) {
		build_dokan(&compiler, version_major);
	};
}