Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust cargo.toml specify custom path for C linker and compiler

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.

like image 209
CoffeDeveloper Avatar asked Jun 14 '20 10:06

CoffeDeveloper


People also ask

Where do you put Cargo in toml?

Cargo. toml and Cargo. lock are stored in the root of your project (package root). Source code goes in the src directory.

Where do I put build RS files?

Placing a file named build.rs in the root of a package will cause Cargo to compile that script and execute it just before building the package.

Where is config toml Rust?

Rustup has a TOML settings file at ${RUSTUP_HOME}/settings. toml (which defaults to ~/. rustup or %USERPROFILE%/. rustup ).

What is Out_dir?

OUT_DIR — the folder in which all output and intermediate artifacts should be placed. This folder is inside the build directory for the package being built, and it is unique for the package in question. TARGET — the target triple that is being compiled for. Native code should be compiled for this triple.


1 Answers

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.

like image 192
Chase Avatar answered Oct 06 '22 23:10

Chase