Skip to content

Commit

Permalink
Merge pull request #126 from bell345/bell345/fix-windows
Browse files Browse the repository at this point in the history
Enum and link fixes to enable building on Windows using MSVC
  • Loading branch information
nlfiedler authored Jul 27, 2024
2 parents d50f01b + e399e0d commit 958e3f6
Show file tree
Hide file tree
Showing 43 changed files with 270 additions and 2,261 deletions.
61 changes: 34 additions & 27 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,38 +45,45 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

## Installing on Windows

So far nothing works. There are several problems that must be overcome:

* Get an appropriate version of LLVM/Clang for your architecture (32 or 64 bit).
* Get an appropriate version of ImageMagick.
* Get the generated bindings to compile without error.

[MSYS2](https://www.msys2.org/) looks neat and might work, but the compiled binary will only run with MSYS2.

For LLVM/Clang, I found more success with the _windows_ file on the project [releases page](https://github.com/llvm/llvm-project/releases); it is a `.tar.xz` file that might be tricky to extract, but at least it seems to work. Other versions would fail during build time due to an error in the `LoadLibraryExW` function.

The remaining problem, I believe, is getting an appropriate version of ImageMagick. I tried the _dll_ installers both with and without HDRI, but both resulted in build failures. The other _static_ files do not have any `.dll` files so they are not useful for building magick-rust.

My conclusion is that building magick-rust on Windows is not possible. If you do find a way to build a portable binary that does not require a separate subsystem, such as MSYS2, please share extremely detailed and repeatable instructions. Thank you.

### Nathan's notes

This section will be replaced by working instructions, if any can ever be found.
Currently, the only way to build on Windows is from source, as the `.lib` files have been removed from the binary releases (see [ImageMagick#7272](https://github.com/ImageMagick/ImageMagick/issues/7272)). You will need to follow the below steps carefully.

1. Ensure you have installed Git and LLVM. The easiest way to do this on Windows is by using `winget`:
```powershell
winget install Git.Git
winget install LLVM.LLVM
```
$Env:IMAGE_MAGICK_DIR = 'C:\bin\ImageMagick-7.1.1-Q16'
$Env:LIBCLANG_PATH = 'C:\bin\clang+llvm-18.1.7-x86_64-pc-windows-msvc\bin'
2. Ensure you have installed Visual Studio 2022, with the "C++ MFC for latest build tools (x86 & x64)".
3. Clone the [ImageMagick-Windows](https://github.com/ImageMagick/ImageMagick-Windows) repository to a well known place. The following instructions assume `C:\IM7`, but the choice does not matter:
```powershell
git clone https://github.com/ImageMagick/ImageMagick-Windows C:\IM7
```

The weird build error:

4. Run the `CloneRepositories.IM7.cmd` batch file from the source directory, but take care to include the SHA hash of the latest [ImageMagick](https://github.com/ImageMagick/ImageMagick) release (e.g. d775d2a for [7.1.1-35](https://github.com/ImageMagick/ImageMagick/releases/tag/7.1.1-35)):
```powershell
cd C:\IM7
.\CloneRepositories.IM7.cmd d775d2a
```
5. With Visual Studio 2022, open the `C:\IM7\Configure\Configure.sln` solution.
6. Build and run this application. You can use Ctrl+F5 as a shortcut.
7. Using the wizard, configure for "Dynamic Multi-Threaded DLL Runtimes". You can leave everything else as defaults.
8. Open the generated `C:\IM7\IM7.Dynamic.x64.sln` solution.
9. Change the run configuration from Debug to Release mode.
10. Build the solution using "Build Solution" under the "Build" menu, or press Ctrl+Shift+B.
12. Get a cup of coffee, because this will take a while to finish compiling.
13. Search for "Edit the system environment variables" in the Start menu and click on "Environment Variables..."
14. Add the following as system or user environment variables (replacing `C:\IM7` as appropriate):
```ini
IMAGE_MAGICK_DIR=C:\IM7\Output
IMAGE_MAGICK_INCLUDE_DIRS=C:\IM7\ImageMagick
```
error[E0308]: mismatched types
--> src\types\style_type.rs:26:12
|
26 | Bold = bindings::StyleType_BoldStyle,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `u32`, found `i32`
15. Add the following directory to your `PATH` variable:
```
C:\IM7\Output\bin
```
16. Once you have restarted your IDE or terminal to pick up on the changes, you may run `cargo build` in your project that includes `magick_rust` to confirm that ImageMagick is linked successfully.

__NOTE:__ Keep in mind that these instructions will *dynamically* link your Rust application with the ImageMagick DLLs. Thus, when distributing your application, you will either need to provide these DLLs (found as `C:\IM7\Output\bin\*_RL_*_.dll`) in the same directory as your executable, or get your users to install the ImageMagick binary distribution.

A set of instructions to enable static linkage of ImageMagick on Windows has yet to be found.

## Creating an Example

Expand Down
12 changes: 2 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Because this crate is generating bindings for a C/C++ library, there are several
- [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 [INSTALL.md](./INSTALL.md) guide
- Windows: download `*-dll` [installer](https://www.imagemagick.org/script/download.php#windows). When installing, check the *Install development headers and libraries for C and C++* checkbox.
- Windows currently requires building from source, see [INSTALL.md](./INSTALL.md#installing-on-windows)
* [Clang](https://clang.llvm.org) (version 5.0 or higher, as dictated by [rust-bindgen](https://github.com/rust-lang/rust-bindgen))
* Windows requires MSVC toolchain
- Download the [Microsoft C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) and select the `MSVC ... build tools` (latest version with appropriate architecture) and `Windows 11 SDK` (or `10` if using Windows 10).
Expand All @@ -38,15 +38,7 @@ If `pkg-config` is not available, or you wish to override its behavior, you can

### Build on Windows

When building on Windows, you will need to set the `IMAGE_MAGICK_DIR` environment variable to point to the ImageMagick installation path. Maybe this is possible with the `set` command, but it may be necessary to set the variable in the system preferences. Without setting `IMAGE_MAGICK_DIR`, the `build.rs` script will try to run `pkg-config` which is a tool generally found on Unix-based systems.

```shell
$Env:IMAGE_MAGICK_DIR = '<path\to\imagemagick>'
cargo build
cargo test
```

If you are having trouble building on Windows, you are not alone. See the [INSTALL.md](./INSTALL.md) guide for the current state of affairs.
At the moment, building on Windows requires building from source. See [INSTALL.md](./INSTALL.md#installing-on-windows) for guidance.

## Documentation

Expand Down
190 changes: 157 additions & 33 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
extern crate bindgen;
extern crate pkg_config;

use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::env;
use std::fs::File;
use std::io::prelude::*;
Expand Down Expand Up @@ -47,8 +47,73 @@ pub const PATH_SEPARATOR: &str = match cfg!(target_os = "windows") {
_ => ":",
};

#[derive(Debug)]
struct IgnoreMacros {
macros_to_ignore: HashSet<String>
}

impl IgnoreMacros {
fn from_iter<S, I>(macro_names: I) -> Self
where
S: Into<String>,
I: IntoIterator<Item = S>
{
let mut macros_to_ignore = HashSet::new();
for macro_name in macro_names {
macros_to_ignore.insert(macro_name.into());
}
Self {
macros_to_ignore
}
}
}

impl bindgen::callbacks::ParseCallbacks for IgnoreMacros {
fn will_parse_macro(&self, name: &str) -> bindgen::callbacks::MacroParsingBehavior {
if self.macros_to_ignore.contains(name) {
bindgen::callbacks::MacroParsingBehavior::Ignore
} else {
bindgen::callbacks::MacroParsingBehavior::Default
}
}
}

#[derive(Debug)]
struct RemoveEnumVariantSuffixes {
names_to_suffix: HashMap<String, String>
}

impl RemoveEnumVariantSuffixes {
fn from_iter<S, I>(enum_suffix_pairs: I) -> Self
where
S: Into<String>,
I: IntoIterator<Item = (S, S)>,
{
let mut names_to_suffix = HashMap::new();
for (enum_name, variant_suffix) in enum_suffix_pairs {
names_to_suffix.insert(enum_name.into(), variant_suffix.into());
}

Self {
names_to_suffix
}
}
}

impl bindgen::callbacks::ParseCallbacks for RemoveEnumVariantSuffixes {
fn enum_variant_name(
&self,
enum_name: Option<&str>,
original_variant_name: &str,
_variant_value: bindgen::callbacks::EnumVariantValue
) -> Option<String> {
let suffix = self.names_to_suffix.get(enum_name?)?;
Some(original_variant_name.trim_end_matches(suffix).to_string())
}
}

fn main() {
let check_cppflags = if cfg!(target_os = "windows") {
let check_cppflags = if cfg!(all(target_os = "windows", not(target_env = "msvc"))) {
// Resolve bash from directories listed in the PATH environment variable in the
// order they appear.
Command::new("cmd")
Expand Down Expand Up @@ -94,7 +159,7 @@ fn main() {
Ok(ref v) => v.split(PATH_SEPARATOR).map(|x| x.to_owned()).collect(),
Err(_) => {
if target.contains("windows") {
vec!["CORE_RL_MagickWand_".to_string()]
vec!["CORE_RL_MagickWand_".to_string(), "CORE_RL_MagickCore_".to_string()]
} else if target.contains("freebsd") {
vec!["MagickWand-7".to_string()]
} else {
Expand All @@ -112,36 +177,92 @@ fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let bindings_path_str = out_dir.join("bindings.rs");

#[derive(Debug)]
struct IgnoreMacros(HashSet<String>);

impl bindgen::callbacks::ParseCallbacks for IgnoreMacros {
fn will_parse_macro(&self, name: &str) -> bindgen::callbacks::MacroParsingBehavior {
if self.0.contains(name) {
bindgen::callbacks::MacroParsingBehavior::Ignore
} else {
bindgen::callbacks::MacroParsingBehavior::Default
}
}
}
let ignored_macros = IgnoreMacros::from_iter([
"FP_INFINITE",
"FP_NAN",
"FP_NORMAL",
"FP_SUBNORMAL",
"FP_ZERO",
"IPPORT_RESERVED",
"FP_INT_UPWARD",
"FP_INT_DOWNWARD",
"FP_INT_TOWARDZERO",
"FP_INT_TONEARESTFROMZERO",
"FP_INT_TONEAREST",
]);

let ignored_macros = IgnoreMacros(
vec![
"FP_INFINITE".into(),
"FP_NAN".into(),
"FP_NORMAL".into(),
"FP_SUBNORMAL".into(),
"FP_ZERO".into(),
"IPPORT_RESERVED".into(),
"FP_INT_UPWARD".into(),
"FP_INT_DOWNWARD".into(),
"FP_INT_TOWARDZERO".into(),
"FP_INT_TONEARESTFROMZERO".into(),
"FP_INT_TONEAREST".into(),
]
.into_iter()
.collect(),
);
let remove_enum_suffixes = RemoveEnumVariantSuffixes::from_iter([
("ClassType", "Class"),
("CompositeOperator", "CompositeOp"),
("GravityType", "Gravity"),
("ImageType", "Type"),
("InterlaceType", "Interlace"),
("OrientationType", "Orientation"),
("ResolutionType", "Resolution"),
("TransmitType", "TransmitType"),
("MapMode", "Mode"),
("ColorspaceType", "Colorspace"),
("ChannelType", "Channel"),
("PixelChannel", "PixelChannel"),
("PixelIntensityMethod", "PixelIntensityMethod"),
("PixelInterpolateMethod", "InterpolatePixel"),
("PixelMask", "PixelMask"),
("PixelTrait", "PixelTrait"),
("VirtualPixelMethod", "VirtualPixelMethod"),
("ComplianceType", "Compliance"),
("IlluminantType", "Illuminant"),
("CompressionType", "Compression"),
("KernelInfoType", "Kernel"),
("MorphologyMethod", "Morphology"),
("PreviewType", "Preview"),
("DisposeType", "Dispose"),
("LayerMethod", "Layer"),
("RenderingIntent", "Intent"),
("EndianType", "Endian"),
("QuantumAlphaType", "QuantumAlpha"),
("QuantumFormat", "QuantumFormat"),
("QuantumType", "Quantum"),
("FilterType", "Filter"),
("TimerState", "TimerState"),
("StretchType", "Stretch"),
("StyleType", "Style"),
("AlignType", "Align"),
("DecorationType", "Decoration"),
("DirectionType", "Direction"),
("FillRule", "Rule"),
("GradientType", "Gradient"),
("LineCap", "Cap"),
("LineJoin", "Join"),
("PaintMethod", "Method"),
("PrimitiveType", "Primitive"),
("ReferenceType", "Reference"),
("SpreadMethod", "Spread"),
("WordBreakType", "WordBreakType"),
("CacheType", "Cache"),
("AlphaChannelOption", "AlphaChannel"),
("MetricType", "ErrorMetric"),
("MagickFormatType", "FormatType"),
("MagickInfoFlag", "Flag"),
("DistortMethod", "Distortion"),
("SparseColorMethod", "ColorInterpolate"),
("ComplexOperator", "ComplexOperator"),
("MontageMode", "Mode"),
("MagickCLDeviceType", "DeviceType"),
("CommandOption", "Options"),
("ValidateType", "Validate"),
("CommandOptionFLags", "OptionFlag"),
("PolicyDomain", "PolicyDomain"),
("PolicyRights", "PolicyRights"),
("DitherMethod", "DitherMethod"),
("RegistryType", "RegistryType"),
("ResourceType", "Resource"),
("MagickEvaluateOperator", "EvaluateOperator"),
("MagickFunction", "Function"),
("StatisticType", "Statistic"),
("AutoThresholdMethod", "ThresholdMethod"),
("PathType", "Path"),
("NoiseType", "Noise")
]);

if !Path::new(&bindings_path_str).exists() {
// Create the header file that rust-bindgen needs as input.
Expand All @@ -159,8 +280,11 @@ fn main() {
.header(gen_h_path.to_str().unwrap())
.size_t_is_usize(true)
.parse_callbacks(Box::new(ignored_macros))
.parse_callbacks(Box::new(remove_enum_suffixes))
.blocklist_type("timex")
.blocklist_function("clock_adjtime");
.blocklist_function("clock_adjtime")
.default_enum_style(bindgen::EnumVariation::Rust { non_exhaustive: false })
.derive_eq(true);

for d in include_dirs {
builder = builder.clang_arg(format!("-I{}", d.to_string_lossy()));
Expand Down
31 changes: 11 additions & 20 deletions src/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,20 @@
*/
use super::bindings;

pub trait FromRust<T> {
fn from_rust(t: T) -> Self;
}

impl FromRust<bool> for bindings::MagickBooleanType {
fn from_rust(b: bool) -> Self {
if b {
bindings::MagickBooleanType_MagickTrue
} else {
bindings::MagickBooleanType_MagickFalse
impl From<bindings::MagickBooleanType> for bool {
fn from(value: bindings::MagickBooleanType) -> Self {
match value {
bindings::MagickBooleanType::MagickFalse => false,
bindings::MagickBooleanType::MagickTrue => true
}
}
}

pub trait ToMagick<T> {
fn to_magick(self) -> T;
}

impl<T, E> ToMagick<T> for E
where
T: FromRust<E>,
{
fn to_magick(self) -> T {
<T as FromRust<E>>::from_rust(self)
impl From<bool> for bindings::MagickBooleanType {
fn from(value: bool) -> Self {
match value {
true => bindings::MagickBooleanType::MagickTrue,
false => bindings::MagickBooleanType::MagickFalse
}
}
}
Loading

0 comments on commit 958e3f6

Please sign in to comment.