diff --git a/Cargo.toml b/Cargo.toml index c1e815ec7eb..f5a84618ae0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,8 @@ mixer = [] use-bindgen = ["sdl2-sys/use-bindgen"] use-pkgconfig = ["sdl2-sys/use-pkgconfig"] use_mac_framework = ["sdl2-sys/use_mac_framework"] +bundled = ["sdl2-sys/bundled"] +static-link= ["sdl2-sys/static-link"] [[example]] name = "animation" diff --git a/sdl2-sys/Cargo.toml b/sdl2-sys/Cargo.toml index 269d01667af..921f0051408 100644 --- a/sdl2-sys/Cargo.toml +++ b/sdl2-sys/Cargo.toml @@ -16,16 +16,37 @@ name = "sdl2_sys" path = "src/lib.rs" [build-dependencies.bindgen] -version = "0.30" +version = "0.31" optional = true [build-dependencies.pkg-config] version = "0.3.9" optional = true +[build-dependencies.cmake] +version = "0.1" +optional = true + +[build-dependencies.reqwest] +version = "0.7" +optional = true + +[build-dependencies.tar] +version = "0.4" +optional = true + +[build-dependencies.flate2] +version = "0.2" +optional = true + +[build-dependencies] +cfg-if = "0.1" + [features] default = [] use-pkgconfig = ["pkg-config"] use-bindgen = ["bindgen"] +static-link = [] use_mac_framework = [] +bundled = ["cmake", "reqwest", "tar", "flate2"] diff --git a/sdl2-sys/build.rs b/sdl2-sys/build.rs index f40b0a1ea3f..63c4bc733b3 100644 --- a/sdl2-sys/build.rs +++ b/sdl2-sys/build.rs @@ -4,46 +4,232 @@ extern crate pkg_config; #[cfg(feature = "bindgen")] extern crate bindgen; +#[cfg(feature="bundled")] +extern crate cmake; +#[cfg(feature="bundled")] +extern crate tar; +#[cfg(feature="bundled")] +extern crate flate2; +#[cfg(feature="bundled")] +extern crate reqwest; -use std::path::PathBuf; -use std::env; -use std::fs; +#[macro_use] +extern crate cfg_if; -const SDL2_BUNDLED_VERSION: &str = "2.0.6"; +use std::path::{Path, PathBuf}; +use std::{io, fs, env}; + +// corresponds to the headers that we have in sdl2-sys/SDL2-{version} +const SDL2_HEADERS_BUNDLED_VERSION: &str = "2.0.6"; + +// means the lastest stable version that can be downloaded from SDL2's source +const LASTEST_SDL2_VERSION: &str = "2.0.5"; + +#[cfg(feature="bundled")] +fn download_to(url: &str, mut dest: T) { + use io::BufRead; + + let resp = reqwest::get(url).expect(&format!("Failed to GET resource: {:?}", url)); + let size = resp.headers() + .get::() + .map(|ct_len| **ct_len) + .unwrap_or(0); + if !resp.status().is_success() { panic!("Download request failed with status: {:?}", resp.status()) } + if size == 0 { panic!("Size of content was returned was 0") } + + let mut src = io::BufReader::new(resp); + loop { + let n = { + let mut buf = src.fill_buf().unwrap(); + dest.write_all(&mut buf).unwrap(); + buf.len() + }; + if n == 0 { break; } + src.consume(n); + } +} + +#[cfg(feature = "use-pkgconfig")] +#[cfg(feature = "static-link")] +fn get_pkg_config() -> pkg_config::Library { + pkg_config::Config::new() + .statik(true) + .probe("sdl2").unwrap() +} + +#[cfg(feature = "use-pkgconfig")] +#[cfg(not(feature = "static-link"))] +fn get_pkg_config() -> pkg_config::Library { + pkg_config::Config::new() + .statik(false) + .probe("sdl2").unwrap() +} + +// returns the location of the downloaded source +#[cfg(feature = "bundled")] +fn download_sdl2() -> PathBuf { + let out_dir = env::var("OUT_DIR").unwrap(); + + let sdl2_archive_name = format!("SDL2-{}.tar.gz", LASTEST_SDL2_VERSION); + let sdl2_archive_url = format!("http://libsdl.org/release/{}", sdl2_archive_name); + + let sdl2_archive_path = Path::new(&out_dir).join(sdl2_archive_name); + let sdl2_build_path = Path::new(&out_dir).join(format!("SDL2-{}", LASTEST_SDL2_VERSION)); + + // avoid re-downloading the archive if it already exists + if !sdl2_archive_path.exists() { + let sdl2_archive = fs::File::create(&sdl2_archive_path).unwrap(); + download_to(&sdl2_archive_url, &sdl2_archive); + } + + let reader = flate2::read::GzDecoder::new( + fs::File::open(&sdl2_archive_path).unwrap() + ).unwrap(); + let mut ar = tar::Archive::new(reader); + ar.unpack(&out_dir).unwrap(); + + sdl2_build_path +} + +// compile a shared or static lib depending on the feature +#[cfg(feature = "bundled")] +fn compile_sdl2(sdl2_build_path: &Path) -> PathBuf { + let install_path = if cfg!(feature = "static-link") { + cmake::Config::new(sdl2_build_path) + .define("SDL_SHARED", "OFF") + .define("SDL_STATIC", "ON") + .build() + } else { + cmake::Config::new(sdl2_build_path) + .define("SDL_SHARED", "ON") + .define("SDL_STATIC", "OFF") + .build() + }; + + install_path +} + +#[cfg(not(feature = "bundled"))] +fn compute_include_paths() -> Vec { + let mut include_paths: Vec = vec!(); + + if let Ok(include_path) = env::var("SDL2_INCLUDE_PATH") { + include_paths.push(format!("{}", include_path)); + }; + + #[cfg(feature = "pkg-config")] { + // don't print the "cargo:xxx" directives, we're just trying to get the include paths here + let pkg_config_library = pkg_config::Config::new().print_system_libs(false).probe("sdl2").unwrap(); + for path in pkg_config_library.include_paths { + include_paths.push(format!("{}", path.display())); + }; + } + + include_paths +} + +fn link_sdl2(target_os: &str) { + #[cfg(all(feature = "use-pkgconfig", not(feature = "bundled")))] { + // prints the appropriate linking parameters when using pkg-config + // useless when using "bundled" + get_pkg_config(); + } + + #[cfg(not(feature = "static-link"))] { + if target_os == "ios" { + // iOS requires additional linking to function properly + println!("cargo:rustc-flags=-l framework=AVFoundation"); + println!("cargo:rustc-flags=-l framework=AudioToolbox"); + println!("cargo:rustc-flags=-l framework=CoreAudio"); + println!("cargo:rustc-flags=-l framework=CoreGraphics"); + println!("cargo:rustc-flags=-l framework=CoreMotion"); + println!("cargo:rustc-flags=-l framework=Foundation"); + println!("cargo:rustc-flags=-l framework=GameController"); + println!("cargo:rustc-flags=-l framework=OpenGLES"); + println!("cargo:rustc-flags=-l framework=QuartzCore"); + println!("cargo:rustc-flags=-l framework=UIKit"); + } + + // pkg-config automatically prints this output when probing, + // however pkg_config isn't used with the feature "bundled" + if cfg!(feature = "bundled") || cfg!(not(feature = "use-pkgconfig")) { + if cfg!(feature = "use_mac_framework") && target_os == "darwin" { + println!("cargo:rustc-flags=-l framework=SDL2"); + } else { + println!("cargo:rustc-flags=-l SDL2"); + } + } + } + + #[cfg(feature = "static-link")] { + if cfg!(feature = "bundled") || cfg!(feature = "use-pkgconfig") == false { + println!("cargo:rustc-link-lib=static=SDL2main"); + println!("cargo:rustc-link-lib=static=SDL2"); + } + + // Also linked to any required libraries for each supported platform + if target_os == "windows-msvc" { + println!("cargo:rustc-link-lib=user32"); + println!("cargo:rustc-link-lib=gdi32"); + println!("cargo:rustc-link-lib=winmm"); + println!("cargo:rustc-link-lib=imm32"); + println!("cargo:rustc-link-lib=ole32"); + println!("cargo:rustc-link-lib=oleaut32"); + println!("cargo:rustc-link-lib=version"); + println!("cargo:rustc-link-lib=uuid"); + println!("cargo:rustc-link-lib=dinput8"); + println!("cargo:rustc-link-lib=dxguid"); + } else if target_os.contains("linux") { + println!("cargo:rustc-link-lib=sndio"); + } else if target_os == "darwin" { + println!("cargo:rustc-link-lib=framework=Cocoa"); + println!("cargo:rustc-link-lib=framework=IOKit"); + println!("cargo:rustc-link-lib=framework=Carbon"); + println!("cargo:rustc-link-lib=framework=ForceFeedback"); + println!("cargo:rustc-link-lib=framework=CoreVideo"); + println!("cargo:rustc-link-lib=framework=CoreAudio"); + println!("cargo:rustc-link-lib=framework=AudioToolbox"); + println!("cargo:rustc-link-lib=iconv"); + } else { + // TODO: Add other platform linker options here. + } + } +} fn main() { let target = env::var("TARGET").expect("Cargo build scripts always have TARGET"); let host = env::var("HOST").expect("Cargo build scripts always have HOST"); + let target_os = get_os_from_triple(target.as_str()).unwrap(); + + #[cfg(feature = "bundled")] { + let sdl2_source_path = download_sdl2(); + let sdl2_compiled_path = compile_sdl2(sdl2_source_path.as_path()); + + let sdl2_downloaded_include_path = sdl2_source_path.join("include"); + let sdl2_compiled_lib_path = sdl2_compiled_path.join("lib"); - prepare_bindings(&target, &host); - - if get_os_from_triple(&target).unwrap() == "ios" { - println!("cargo:rustc-flags=-l framework=AVFoundation"); - println!("cargo:rustc-flags=-l framework=AudioToolbox"); - println!("cargo:rustc-flags=-l framework=CoreAudio"); - println!("cargo:rustc-flags=-l framework=CoreGraphics"); - println!("cargo:rustc-flags=-l framework=CoreMotion"); - println!("cargo:rustc-flags=-l framework=Foundation"); - println!("cargo:rustc-flags=-l framework=GameController"); - println!("cargo:rustc-flags=-l framework=OpenGLES"); - println!("cargo:rustc-flags=-l framework=QuartzCore"); - println!("cargo:rustc-flags=-l framework=UIKit"); + println!("cargo:rustc-link-search={}", sdl2_compiled_lib_path.display()); + + #[cfg(feature = "bindgen")] { + let include_paths = vec!(String::from(sdl2_downloaded_include_path.to_str().unwrap())); + generate_bindings(target.as_str(), host.as_str(), include_paths.as_slice()) + } + }; + + #[cfg(all(not(feature = "bundled"), feature = "bindgen"))] { + let include_paths: Vec = compute_include_paths(); + generate_bindings(target.as_str(), host.as_str(), include_paths.as_slice()) } -} -#[cfg(not(feature = "pkg-config"))] -fn build_pkgconfig() -> bool { - false -} + #[cfg(not(feature = "bindgen"))] { + copy_pregenerated_bindings(); + } -#[cfg(feature = "pkg-config")] -fn build_pkgconfig() -> bool { - pkg_config::probe_library("sdl2").is_ok() + link_sdl2(target_os); } #[cfg(not(feature = "bindgen"))] -fn prepare_bindings(target: &str, host: &str) { - add_explicit_linker_flags(get_os_from_triple(target).unwrap()); +fn copy_pregenerated_bindings() { let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); let crate_path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); fs::copy(crate_path.join("pregenerated_bindings.rs"), out_path.join("bindings.rs")) @@ -51,36 +237,43 @@ fn prepare_bindings(target: &str, host: &str) { } #[cfg(feature = "bindgen")] -fn prepare_bindings(target: &str, host: &str) { +// headers_path is a list of directories where the SDL2 headers are expected +// to be found by bindgen (should point to the include/ directories) +fn generate_bindings + ::std::fmt::Debug>(target: &str, host: &str, headers_paths: &[S]) { + let target_os = get_os_from_triple(target).unwrap(); let mut bindings = bindgen::Builder::default(); - // Set correct target triple when cross-compiling + // Set correct target triple for bindgen when cross-compiling if target != host { bindings = bindings.clang_arg("-target"); bindings = bindings.clang_arg(target.clone()); } - if let Ok(include_path) = env::var("SDL2_INCLUDE_PATH") { - bindings = bindings.clang_arg(String::from("-I") + &include_path); - add_explicit_linker_flags(get_os_from_triple(target).unwrap()); - } else if build_pkgconfig() { - #[cfg(feature = "pkg-config")] - for path in &pkg_config::find_library("sdl2").unwrap().include_paths { - bindings = bindings.clang_arg(String::from("-I") + - &path.clone().into_os_string().into_string().unwrap()); - } - } else { + if headers_paths.len() == 0 { + // if no paths are being provided, fall back to the headers included in this repo let mut include_path: PathBuf = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - include_path.push(String::from("SDL2-") + SDL2_BUNDLED_VERSION); + include_path.push(format!("SDL2-{}", SDL2_HEADERS_BUNDLED_VERSION)); include_path.push("include"); - bindings = bindings.clang_arg(String::from("-I") + - &include_path.into_os_string().into_string().unwrap()); - // SDL2 hasn't a default configuration for Linux - if get_os_from_triple(target).unwrap() == "linux" { - bindings = bindings.clang_arg("-DSDL_VIDEO_DRIVER_X11"); - bindings = bindings.clang_arg("-DSDL_VIDEO_DRIVER_WAYLAND"); + bindings = bindings.clang_arg(format!("-I{}", include_path.display())); + } else { + // if paths are included, use them for bindgen. Bindgen should use the first one. + for headers_path in headers_paths { + bindings = bindings.clang_arg(format!("-I{}", headers_path.as_ref())) } - add_explicit_linker_flags(get_os_from_triple(target).unwrap()); + } + + if target_os == "windows-msvc" { + bindings = bindings.clang_arg(format!("-IC:/Program Files (x86)/Windows Kits/8.1/Include/shared")); + bindings = bindings.clang_arg(format!("-IC:/Program Files/LLVM/lib/clang/5.0.0/include")); + bindings = bindings.clang_arg(format!("-IC:/Program Files (x86)/Windows Kits/10/Include/10.0.10240.0/ucrt")); + bindings = bindings.clang_arg(format!("-IC:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include")); + bindings = bindings.clang_arg(format!("-IC:/Program Files (x86)/Windows Kits/8.1/Include/um")); + }; + + // SDL2 hasn't a default configuration for Linux + if target_os == "linux" { + bindings = bindings.clang_arg("-DSDL_VIDEO_DRIVER_X11"); + bindings = bindings.clang_arg("-DSDL_VIDEO_DRIVER_WAYLAND"); } let bindings = bindings @@ -104,13 +297,5 @@ fn prepare_bindings(target: &str, host: &str) { fn get_os_from_triple(triple: &str) -> Option<&str> { - triple.split("-").nth(2) -} - -fn add_explicit_linker_flags(target_os: &str) { - if cfg!(feature = "use_mac_framework") && target_os == "darwin" { - println!("cargo:rustc-flags=-l framework=SDL2"); - } else { - println!("cargo:rustc-flags=-l SDL2"); - } + triple.splitn(3, "-").nth(2) }