diff --git a/README.md b/README.md index af3a694..9862756 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,17 @@ A somewhat safe Rust interface to the [ImageMagick](http://www.imagemagick.org/) ## Dependencies -* Rust (~latest release) +* Rust (~latest release) (on Windows you will require MSVC version) * Cargo (~latest release) * ImageMagick (version 7.0.x) - Does _not_ work with ImageMagick 6.x due to backward incompatible changes. - [FreeBSD](https://www.freebsd.org): `sudo pkg install ImageMagick7` - [Homebrew](http://brew.sh): `brew install imagemagick` - Linux may require building ImageMagick from source, see the `Dockerfile` for an example + - Windows: download `*-dll` [installer](https://www.imagemagick.org/script/binary-releases.php#windows). Only MSVC version available. When installing, check the checkbox "Install development headers and libraries for C and C++". * [Clang](https://clang.llvm.org) (version 3.5 or higher) - Or whatever version is dictated by [rust-bindgen](https://github.com/servo/rust-bindgen) -* `pkg-config`, to facilitate linking with ImageMagick. +* Optionally `pkg-config`, to facilitate linking with ImageMagick. Or you can set linker parameters via environment variables. ## Build and Test @@ -24,6 +25,25 @@ $ cargo build $ cargo test ``` +Optionally you can set some environment variables before building: +* `IMAGE_MAGICK_DIR` - installation directory of ImageMagick +* `IMAGE_MAGICK_LIB_DIRS` - list of lib directories split by `:` +* `IMAGE_MAGICK_INCLUDE_DIRS` - list of include directories split by `:` +* `IMAGE_MAGICK_LIBS` - list of the libs to link to + +### Build on Windows + +On Windows you will need to launch build in Visual Studio Native Tools Command Prompt. +It can be found in *Start menu -> Visual Studio < VERSION > -> Visual Studio Tools -> Windows Desktop Command Prompts*. +Choose the architecture corresponding to architecture of your rust compiler. +This is required for proper work of `rust-bindgen`. + +``` +> set IMAGE_MAGICK_DIR= +> cargo build +> cargo test +``` + ## Example Usage MagickWand has some global state that needs to be initialized prior to using the library, but fortunately Rust makes handling this pretty easy. In the example below, we read in an image from a file and resize it to fit a square of 240 by 240 pixels, then convert the image to JPEG. diff --git a/build.rs b/build.rs index ed9b436..311ed83 100644 --- a/build.rs +++ b/build.rs @@ -16,6 +16,7 @@ extern crate bindgen; extern crate pkg_config; +use std::collections::HashSet; use std::env; use std::fs::File; use std::io::prelude::*; @@ -28,31 +29,64 @@ const MAX_VERSION: &'static str = "7.1"; static HEADER: &'static str = "#include \n"; fn main() { - // Assert that the appropriate version of MagickWand is installed, - // since we are very dependent on the particulars of MagickWand. - pkg_config::Config::new() - .atleast_version(MIN_VERSION) - .probe("MagickWand") - .unwrap(); - // Check the maximum version separately as pkg-config will ignore that - // option when combined with (certain) other options. And since the - // pkg-config crate always adds those other flags, we must run the - // command directly. - if !Command::new("pkg-config") - .arg(format!("--max-version={}", MAX_VERSION)) - .arg("MagickWand") - .status().unwrap().success() { - panic!(format!("MagickWand version must be no higher than {}", MAX_VERSION)); + if cfg!(target_os = "freebsd") { + // pkg_config does not seem to work properly on FreeBSD, so + // hard-code the builder settings for the time being. + env_var_set_default("IMAGE_MAGICK_INCLUDE_DIRS", "/usr/local/include/ImageMagick-7"); + // Need to hack the linker flags as well. + env_var_set_default("IMAGE_MAGICK_LIB_DIRS", "/usr/local/lib"); + env_var_set_default("IMAGE_MAGICK_LIBS", "MagickWand-7"); + } + + let target = env::var("TARGET").unwrap(); + + let lib_dirs = find_image_magick_lib_dirs(); + let include_dirs = find_image_magick_include_dirs(); + + + for d in &lib_dirs { + if !d.exists() { + panic!("ImageMagick library directory does not exist: {}", d.to_string_lossy()); + } + println!( "cargo:rustc-link-search=native={}", d.to_string_lossy()); + } + + for d in &include_dirs { + if !d.exists() { + panic!("ImageMagick include directory does not exist: {}", d.to_string_lossy()); + } + println!("cargo:include={}", d.to_string_lossy()); + } + + // let version = validate_headers(&[include_dir.clone().into()]); + + println!("cargo:rerun-if-env-changed=IMAGE_MAGICK_LIBS"); + let libs_env = env::var("IMAGE_MAGICK_LIBS").ok(); + let libs = match libs_env { + Some(ref v) => v.split(":").map(|x| x.to_owned()).collect(), + None => { + if target.contains("windows") { + vec!["CORE_RL_MagickWand_".to_string()] + } + else if target.contains("freebsd") { + vec!["MagickWand-7".to_string()] + } + else { + run_pkg_config().libs + } + } + }; + + let kind = determine_mode(&lib_dirs, libs.as_slice()); + for lib in libs.into_iter() { + println!("cargo:rustc-link-lib={}={}", kind, lib); } - // We have to split the version check and the cflags/libs check because - // you can't do both at the same time on RHEL (apparently). - let library = pkg_config::Config::new().probe("MagickWand").unwrap(); // If the generated bindings are missing, generate them now. let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); let bindings_path_str = out_dir.join("bindings.rs"); - if !Path::new(&bindings_path_str).exists() { + if !Path::new(&bindings_path_str).exists() { // Create the header file that rust-bindgen needs as input. let gen_h_path = out_dir.join("gen.h"); let mut gen_h = File::create(&gen_h_path).expect("could not create file"); @@ -71,17 +105,10 @@ fn main() { .hide_type("FP_SUBNORMAL") .hide_type("FP_NORMAL"); - for include_path in library.include_paths { - builder = builder.clang_arg(format!("-I{}", include_path.to_string_lossy())); - } - if cfg!(target_os = "freebsd") { - // pkg_config does not seem to work properly on FreeBSD, so - // hard-code the builder settings for the time being. - builder = builder.clang_arg("-I/usr/local/include/ImageMagick-7"); - // Need to hack the linker flags as well. - println!("cargo:rustc-link-lib=dylib=MagickWand-7"); - println!("cargo:rustc-link-search=native=/usr/local/lib"); + for d in include_dirs { + builder = builder.clang_arg(format!("-I{}", d.to_string_lossy())); } + let bindings = builder.generate().unwrap(); let mut file = File::create(&bindings_path_str).expect("could not create bindings file"); // Work around the include! issue in rustc (as described in the @@ -94,3 +121,103 @@ fn main() { std::fs::remove_file(&gen_h_path).expect("could not remove header file"); } } + +fn env_var_set_default(name: &str, value: &str) { + if env::var(name).is_err() { + env::set_var(name, value); + } +} + +fn find_image_magick_lib_dirs() -> Vec { + println!("cargo:rerun-if-env-changed=IMAGE_MAGICK_LIB_DIRS"); + env::var("IMAGE_MAGICK_LIB_DIRS") + .map(|x| x.split(":").map(PathBuf::from).collect::>()) + .or_else(|_| Ok(vec![find_image_magick_dir()?.join("lib")])) + .or_else(|_: env::VarError| -> Result<_, env::VarError> { + Ok(run_pkg_config().link_paths) + }) + .expect("Couldn't find ImageMagick library directory") +} + +fn find_image_magick_include_dirs() -> Vec { + println!("cargo:rerun-if-env-changed=IMAGE_MAGICK_INCLUDE_DIRS"); + env::var("IMAGE_MAGICK_INCLUDE_DIRS") + .map(|x| x.split(":").map(PathBuf::from).collect::>()) + .or_else(|_| Ok(vec![find_image_magick_dir()?.join("include")])) + .or_else(|_: env::VarError| -> Result<_, env::VarError> { + Ok(run_pkg_config().include_paths) + }) + .expect("Couldn't find ImageMagick include directory") +} + +fn find_image_magick_dir() -> Result { + println!("cargo:rerun-if-env-changed=IMAGE_MAGICK_DIR"); + env::var("IMAGE_MAGICK_DIR") + .map(PathBuf::from) +} + +fn determine_mode>(libdirs: &Vec, libs: &[T]) -> &'static str { + println!("cargo:rerun-if-env-changed=IMAGE_MAGICK_STATIC"); + let kind = env::var("IMAGE_MAGICK_STATIC").ok(); + match kind.as_ref().map(|s| &s[..]) { + Some("0") => return "dylib", + Some(_) => return "static", + None => {} + } + + // See what files we actually have to link against, and see what our + // possibilities even are. + let files = libdirs.into_iter().flat_map(|d| d.read_dir().unwrap()) + .map(|e| e.unwrap()) + .map(|e| e.file_name()) + .filter_map(|e| e.into_string().ok()) + .collect::>(); + let can_static = libs.iter().all(|l| { + files.contains(&format!("lib{}.a", l.as_ref())) || files.contains(&format!("{}.lib", l.as_ref())) + }); + let can_dylib = libs.iter().all(|l| { + files.contains(&format!("lib{}.so", l.as_ref())) || files.contains(&format!("{}.dll", l.as_ref())) || + files.contains(&format!("lib{}.dylib", l.as_ref())) + }); + + match (can_static, can_dylib) { + (true, false) => return "static", + (false, true) => return "dylib", + (false, false) => { + panic!( + "ImageMagick libdirs at `{:?}` do not contain the required files \ + to either statically or dynamically link ImageMagick", + libdirs + ); + } + (true, true) => {} + } + + // default + "dylib" +} + +fn run_pkg_config() -> pkg_config::Library { + // Assert that the appropriate version of MagickWand is installed, + // since we are very dependent on the particulars of MagickWand. + pkg_config::Config::new() + .cargo_metadata(false) + .atleast_version(MIN_VERSION) + .probe("MagickWand") + .unwrap(); + // Check the maximum version separately as pkg-config will ignore that + // option when combined with (certain) other options. And since the + // pkg-config crate always adds those other flags, we must run the + // command directly. + if !Command::new("pkg-config") + .arg(format!("--max-version={}", MAX_VERSION)) + .arg("MagickWand") + .status().unwrap().success() { + panic!(format!("MagickWand version must be no higher than {}", MAX_VERSION)); + } + // We have to split the version check and the cflags/libs check because + // you can't do both at the same time on RHEL (apparently). + pkg_config::Config::new() + .cargo_metadata(false) + .probe("MagickWand").unwrap() +}