Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automate asio sys build #804

Merged
merged 6 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions asio-sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ keywords = ["audio", "sound", "asio", "steinberg"]
build = "build.rs"

[target.'cfg(any(target_os = "windows"))'.build-dependencies]
bindgen = "0.64"
bindgen = "0.68"
walkdir = "2"
cc = "1.0.25"
cc = "1.0.83"
reqwest = { version = "0.11" , features = ["blocking"] }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should not include reqwest for a tiny task like a build script. it's better to invoke some available windows-specific command that downloads a file from an url. same goes for the zip crate, if possible

zip = "0.6"

[dependencies]
once_cell = "1.12"
Expand Down
151 changes: 141 additions & 10 deletions asio-sys/build.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
extern crate bindgen;
extern crate cc;
extern crate reqwest;
extern crate walkdir;
extern crate zip;

use reqwest::blocking::Client;
use std::env;
use std::fs;
use std::io::{Cursor, Read};
use std::path::PathBuf;
use std::process::{exit, Command};
use walkdir::WalkDir;
use zip::read::ZipArchive;

const CPAL_ASIO_DIR: &str = "CPAL_ASIO_DIR";
const ASIO_SDK_URL: &str = "https://www.steinberg.net/asiosdk";

const ASIO_HEADER: &str = "asio.h";
const ASIO_SYS_HEADER: &str = "asiosys.h";
Expand All @@ -15,24 +23,23 @@ const ASIO_DRIVERS_HEADER: &str = "asiodrivers.h";
fn main() {
println!("cargo:rerun-if-env-changed={}", CPAL_ASIO_DIR);

// If ASIO directory isn't set silently return early
let cpal_asio_dir_var = match env::var(CPAL_ASIO_DIR) {
Err(_) => return,
Ok(var) => var,
};

// Asio directory
let cpal_asio_dir = PathBuf::from(cpal_asio_dir_var);
// ASIO SDK directory
let cpal_asio_dir = get_asio_dir();
println!("cargo:rerun-if-changed={}", cpal_asio_dir.display());

// Directory where bindings and library are created
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("bad path"));

// Check if library exists
// If it doesn't create it
let mut vc_vars_invoked = false;

// Check if library exists,
// if it doesn't create it
let mut lib_path = out_dir.clone();
lib_path.push("libasio.a");
if !lib_path.exists() {
// Set env vars from vcvarsall.bat before attempting to build
invoke_vcvars();
vc_vars_invoked = true;
create_lib(&cpal_asio_dir);
}

Expand All @@ -48,6 +55,10 @@ fn main() {
let mut binding_path = out_dir.clone();
binding_path.push("asio_bindings.rs");
if !binding_path.exists() {
// Set env vars from vcvarsall.bat before attempting to build if not already done
if !vc_vars_invoked {
invoke_vcvars();
}
create_bindings(&cpal_asio_dir);
}
}
Expand Down Expand Up @@ -207,3 +218,123 @@ fn create_bindings(cpal_asio_dir: &PathBuf) {
.write_to_file(out_path.join("asio_bindings.rs"))
.expect("Couldn't write bindings!");
}

fn get_asio_dir() -> PathBuf {
// Check if CPAL_ASIO_DIR env var is set
if let Ok(path) = env::var(CPAL_ASIO_DIR) {
println!("CPAL_ASIO_DIR is set at {CPAL_ASIO_DIR}");
return PathBuf::from(path);
}

// If not set, check temp directory for ASIO SDK, maybe it is previously downloaded
let temp_dir = env::temp_dir();
let asio_dir = temp_dir.join("asio_sdk");
if asio_dir.exists() {
println!("CPAL_ASIO_DIR is set at {}", asio_dir.display());
return asio_dir;
}

// If not found, download ASIO SDK
println!("CPAL_ASIO_DIR is not set or contents are cached downloading from {ASIO_SDK_URL}");
let response = Client::new().get(ASIO_SDK_URL).send();

match response {
Ok(mut resp) => {
if resp.status().is_success() {
println!("Downloaded ASIO SDK successfully");
println!("Extracting ASIO SDK..");
// Unzip the archive
let mut archive_bytes = Vec::new();
resp.read_to_end(&mut archive_bytes).unwrap();

let cursor = Cursor::new(archive_bytes);
let mut archive = ZipArchive::new(cursor).expect("Failed to read zip contents");
archive.extract(&temp_dir).expect("Failed to extract zip");

// Move the contents of the inner directory to asio_dir
for entry in walkdir::WalkDir::new(&temp_dir).min_depth(1).max_depth(1) {
let entry = entry.unwrap();
if entry.file_type().is_dir()
&& entry.file_name().to_string_lossy().starts_with("asio")
{
fs::rename(entry.path(), &asio_dir).expect("Failed to rename directory");
break;
}
}
println!("CPAL_ASIO_DIR is set at {}", asio_dir.display());
asio_dir
} else {
panic!("Failed to download ASIO SDK")
}
}
Err(_) => panic!("Failed to download ASIO SDK"),
}
}

fn invoke_vcvars() {
println!("Invoking vcvarsall.bat..");
println!("Determining system architecture..");

// Determine the system architecture to be used as an argument to vcvarsall.bat
let arch = if cfg!(target_arch = "x86_64") {
"amd64"
} else if cfg!(target_arch = "x86") {
"x86"
} else if cfg!(target_arch = "arm") {
"arm"
} else if cfg!(target_arch = "aarch64") {
"arm64"
} else {
panic!("Unsupported architecture");
};

println!("Architecture detected as {}.", arch);

// Define search paths for vcvarsall.bat based on architecture
let paths = if arch == "amd64" {
vec![
"C:\\Program Files (x86)\\Microsoft Visual Studio\\",
"C:\\Program Files\\Microsoft Visual Studio\\",
]
} else {
vec!["C:\\Program Files\\Microsoft Visual Studio\\"]
};

// Search for vcvarsall.bat using walkdir
println!("Searching for vcvarsall.bat in {paths:?}");
for path in paths.iter() {
for entry in WalkDir::new(path)
.into_iter()
.filter_map(Result::ok)
.filter(|e| !e.file_type().is_dir())
{
if entry.path().ends_with("vcvarsall.bat") {
println!(
"Found vcvarsall.bat at {}. Initializing environment..",
entry.path().display()
);

// Invoke vcvarsall.bat
let output = Command::new("cmd")
.args(&["/c", entry.path().to_str().unwrap(), &arch, "&&", "set"])
.output()
.expect("Failed to execute command");

for line in String::from_utf8_lossy(&output.stdout).lines() {
// Filters the output of vcvarsall.bat to only include lines of the form "VARNAME=VALUE"
let parts: Vec<&str> = line.splitn(2, '=').collect();
if parts.len() == 2 {
env::set_var(parts[0], parts[1]);
println!("{}={}", parts[0], parts[1]);
}
}
return;
}
}
}

eprintln!(
"Error: Could not find vcvarsall.bat. Please install the latest version of Visual Studio."
);
exit(1);
}
Loading