12

I'm having some issues due to my ignorance of cargo setup and the vast amount of documentation.

The cargo.toml file is the current:

[package]
name = "hello"
version = "0.1.0"
authors = ["PC4\\Author"]

[dependencies]
sdl2 = { version = "0.34.1", features = ["bundled", "static-link"] }

The SDL2 dependency is compiled but it is actually using Visual Studio. What I actually want to do is to use a custom compiler from another folder when compiling crate dependencies.

trent
  • 25,033
  • 7
  • 51
  • 90
CoffeDeveloper
  • 7,961
  • 3
  • 35
  • 69
  • As of now, I found to use a C compiler I need a create named cc-rs, which basically creates a executable that builds a C binary (a sort of CMAKE), I guess SDL2 is using that, so in reality I need to find how to pass a parameter to SDL2 crate right? – CoffeDeveloper Jun 17 '20 at 10:01
  • how about using `cc-rs` yourself to build the library? You can make a build script of your own – Chase Jun 17 '20 at 10:16
  • Sdl2 Is too complex to be built witouth using their crates and build scripts, unless I spend myself a good amount of time, eventually I can patch it. – CoffeDeveloper Jun 17 '20 at 10:23
  • so you want to just use another compiler? or do you need to use custom parameters passed into the compiler too? – Chase Jun 17 '20 at 10:25
  • Another compiler Is sufficient for now, the default used is msvc, Maybe I can just remove It from Path when invoking cargo? – CoffeDeveloper Jun 17 '20 at 10:26
  • You're on windows correct? Do you plan to use the gcc compiler from mingw? – Chase Jun 17 '20 at 10:36
  • Added a simple explanation for how to use CMake to build C dependencies to my answer as well – Chase Jun 18 '20 at 05:39

1 Answers1

10

You can specify rust to use the gcc compiler when building dependencies, as long as you've rust installed correctly for mingw. To make sure your rust is correctly configured for mingw - use this thread. Remember, by default Rust for windows will get configured for MSVC, not mingw.

The following steps were originally mentioned in the official rust-sdl2 docs

After you're done with that, you'll need a build script to link the libraries to the dependency. But first, you need the libraries. Download the mingw specific libraries from the official libsdl website

Now you need to put these files in the same folder as the cargo.toml, in correct order-

SDL2-devel-2.0.x-mingw.tar.gz\SDL2-2.0.x\i686-w64-mingw32\bin       ->  gnu-mingw\dll\32
SDL2-devel-2.0.x-mingw.tar.gz\SDL2-2.0.x\x86_64-w64-mingw32\bin     ->  gnu-mingw\dll\64
SDL2-devel-2.0.x-mingw.tar.gz\SDL2-2.0.x\i686-w64-mingw32\lib       ->  gnu-mingw\lib\32
SDL2-devel-2.0.x-mingw.tar.gz\SDL2-2.0.x\x86_64-w64-mingw32\lib     ->  gnu-mingw\lib\64

gnu-mingw should be a folder in the same directory as cargo.toml

Now you need the build script itself, make a file called build.rs and put this in your [package] of cargo.toml

build = "build.rs"

More on build scripts can be found here

Here's the script-

use std::env;
use std::path::PathBuf;

fn main() {
    let target = env::var("TARGET").unwrap();
    if target.contains("pc-windows") {
        let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        let mut lib_dir = manifest_dir.clone();
        let mut dll_dir = manifest_dir.clone();
        lib_dir.push("gnu-mingw");
        dll_dir.push("gnu-mingw");
        lib_dir.push("lib");
        dll_dir.push("dll");
        if target.contains("x86_64") {
            lib_dir.push("64");
            dll_dir.push("64");
        }
        else {
            lib_dir.push("32");
            dll_dir.push("32");
        }
        println!("cargo:rustc-link-search=all={}", lib_dir.display());
        for entry in std::fs::read_dir(dll_dir).expect("Can't read DLL dir")  {
            let entry_path = entry.expect("Invalid fs entry").path();
            let file_name_result = entry_path.file_name();
            let mut new_file_path = manifest_dir.clone();
            if let Some(file_name) = file_name_result {
                let file_name = file_name.to_str().unwrap();
                if file_name.ends_with(".dll") {
                    new_file_path.push(file_name);
                    std::fs::copy(&entry_path, new_file_path.as_path()).expect("Can't copy from DLL dir");
                }
            }
        }
    }
}

Note: This intentionally omits the MSVC specific stuff.

Now in your build config aka [build] inside cargo.toml, you need to put-

target = "x86_64-pc-windows-gnu"

A list of available targets can be found in the cargo build docs

More on build config can be found in the config docs

As a bonus, if you'd like to use some other compiler (other than gcc). All you have to do, is make sure the necessary libraries are in the same directory and put these in your [target.TARGET_NAME]

linker = "path\\to\\c\\linker"
ar = "path\\to\\c\\ar"

substitute TARGET_NAME with your choice target triple.

Edit: As per OP's request to provide information on how to combine CMake with rust.

Using CMake with rust is possible, although, to compile and build a third party dependency will almost definitely require a custom build script that will be able to replace the dependency's own build script.

To illustrate, let's make a custom, simple C static library using CMake with rust.

The following steps were originally mentioned in this flames of code blog

First you need a C project, which doesn't require much except for .c file for now, you should put the .c file inside a directory named libfoo (or whatever your library may be called). Now you may put this libfoo directory in the same directory as your rust project or anywhere you please, but do keep the path in mind.

Go ahead and put a simple "hello world" program in the .c file-

#include <stdio.h>

void testcall(float value)
{
    printf("Hello, world from C! Value passed: %f\n",value);
}

(Note: The function should not be main, as we're building a static library)

Now we need a CMakelists.txt in the same directory-

cmake_minimum_required(VERSION 3.0)
project(LibFoo C)

add_library(foo STATIC foo.c)

install(TARGETS foo DESTINATION .)

It's a pretty simple script, though that last line is important - it makes sure the destination of the library is . - we do have to locate this library later from rust.

So now, the file structure may look like-

.
├── Cargo.lock
├── Cargo.toml
├── libfoo
│   ├── CMakeLists.txt
│   └── foo.c
└── src
    └── main.rs

Now for the rust part, you'll require a build script and the build dependency cmake for your project.

Add the build script to cargo.toml-

[package]
build="build.rs"

And the dependency-

[build-dependencies]
cmake = "0.1.31"

Now in your build.rs, you've to invoke cmake-

extern crate cmake;
use cmake::Config;

fn main()
{
    let dst = Config::new("libfoo").build();       

    println!("cargo:rustc-link-search=native={}", dst.display());
    println!("cargo:rustc-link-lib=static=foo");    
}

.build() part is straightforward, but why are those println!s there?

Those write the necessary commands to stdout, so that cargo can search for the library and link it. This is where the name and destination of your c library comes into play.

Now you can simply execute cargo run, and it'll build the C library as well as your rust project!

You can also run it in verbose mode (-vv), to see detailed output of the C library build.

Now all you have to do, is call the library from your main.rs-

#[link(name="foo", kind="static")]
extern { 
    // this is rustified prototype of the function from our C library
    fn testcall(v: f32); 
}

fn main() {
    println!("Hello, world from Rust!");

    // calling the function from foo library
    unsafe { 
        testcall(3.14159); 
    };
}

Pretty straightforward, however the author of the blog left a note for the extern function-

Note that this prototype requires a bit of manual conversion from C prototype to Rust one. It’s straightforward for simple functions operating on primitive value types, but might be more difficult to craft when more complex data types are involved.

Which points us back to SDL2 crate, Compiling the C libraries it requires, linking them and then building the crate itself will certainly require a lot of tinkering - but I hope this has pointed you to the right direction.

Chase
  • 5,315
  • 2
  • 15
  • 41
  • Thank you this great answer really put togheter a great deal of useful info and effort. you have my gratitude :) – CoffeDeveloper Jun 18 '20 at 09:46
  • But that also means, cargo can't change C compiler, you need a different "Distro", in that Cargo it is still far from being a build system capable as Cmake, for certain tasks. (even If I build something with different compiler I still need the ability to link it with the executable, which is like the same building with C was) – CoffeDeveloper Jun 18 '20 at 09:50
  • 1
    @CoffeDeveloper if you mean - specify the C compiler, you can actually achieve that by setting the `CC` (for C) and `CXX` (for C++) environment variables. Note the original answer *does not* discuss a solution about CMake and hence you've to specify the `target` and even configure a lot of stuff. With CMake, you don't need to do much at all. – Chase Jun 18 '20 at 12:01
  • Yes and no, in example if I override those two env variables, the sdl2 crate get automatically builded with msvc anyway. To get it built with another compiler I have to do some extra work (on windows) – CoffeDeveloper Jun 18 '20 at 12:18
  • 1
    @CoffeDeveloper I'm quite sure sdl2 uses its own build system as I mentioned already - so env variables do nothing. Did you replace all its build system with CMake? It's quite the hefty task and will almost definitely require your own fork of the crate. If you do not want to rely on their own build system, your only choice is to use my original answer and link the correct compilers/linkers. (without cmake) – Chase Jun 18 '20 at 12:21