rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
99.01k stars 12.79k forks source link

rust_eh_personality already defined in dynamic library #126633

Open BenCheung0422 opened 5 months ago

BenCheung0422 commented 5 months ago

Code

Follow the steps to build https://github.com/ldesgoui/discord_game_sdk
This can also happen when directly linking Discord GameSDK libraries by bindgen separately.

Current output

error: linking with `link.exe` failed: exit code: 1169
  |
  = note: "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.40.33807\\bin\\HostX64\\x64\\link.exe" "/NOLOGO" .. (a lot of libraries)
  = note: libstd-af7a289140bfd09b.rlib(std-af7a289140bfd09b.std.9b8e320a6364d5d1-cgu.0.rcgu.o) : error LNK2005: rust_eh_personality already defined in discord_game_sdk.lib(discord_game_sdk.dll)
          D:\..\discord_game_sdk\target\debug\deps\discord_game_sdk_sys-7c33338da05ee15f.exe : fatal error LNK1169: one or more multiply defined symbols found

Desired output

Compile and build successfully

Rationale and extra context

No response

Other cases

No response

Rust Version

rustc 1.79.0 (129f3b996 2024-06-10)
binary: rustc
commit-hash: 129f3b9964af4d4a709d1383930ade12dfe7c081
commit-date: 2024-06-10
host: x86_64-pc-windows-msvc
release: 1.79.0
LLVM version: 18.1.7

Anything else?

No response

BenCheung0422 commented 5 months ago

I may head to use discord-sdk crate instead, although the official SDK should still be usable. By the way, this is my simplified version of discord_game_sdk_sys/build.rs:

use std::{env, fs, path::*};
use std::fs::{create_dir_all, File};
use std::io::Write;
use zip::ZipArchive;

const SDK_VERSION: &str = "2.5.6";

fn main() {
    // DO NOT RELY ON THIS
    if cfg!(feature = "private-docs-rs") {
        return generate_ffi_bindings(bindgen::builder().header("discord_game_sdk.h"));
    }

    let sdk_path = fetch_discord_game_sdk();
    // println!("cargo:rustc-env=LD_LIBRARY_PATH={}", fs::canonicalize(sdk_path.join("lib/x86_64")).unwrap().display());
    // println!("cargo:rustc-env=DYLD_LIBRARY_PATH={}", fs::canonicalize(sdk_path.join("lib/x86_64")).unwrap().display());
    println!("cargo:rustc-link-search={}", fs::canonicalize(sdk_path.join("lib/x86_64")).unwrap().to_str().unwrap());
    println!("cargo:rerun-if-changed={}", sdk_path.display());

    generate_ffi_bindings(
        bindgen::builder().header(sdk_path.join("c/discord_game_sdk.h").to_str().unwrap()),
    );

    if cfg!(feature = "link") {
        let target = env::var("TARGET").unwrap();

        verify_installation(&target, &sdk_path);
        configure_linkage(&target, &sdk_path);
    }
}

fn fetch_discord_game_sdk() -> PathBuf {
    let mut sdk_version = SDK_VERSION.to_string();
    println!("Target Discord GameSDK: {sdk_version}");
    let main_dir = PathBuf::from("libraries");
    let extract_dir = main_dir.join("discord_game_sdk");
    create_dir_all(&extract_dir).unwrap();
    println!("Checking local cache...");

    // Check cached version
    let version_file_path = extract_dir.join("VERSION");
    if version_file_path.exists() {
        match fs::read_to_string(&version_file_path) {
            Ok(ver) => {
                if ver == sdk_version {
                    println!("Version matched, no upgrade required.");
                    return extract_dir;
                } else {
                    println!("Version not matched, an upgrade is required.");
                }
            }
            Err(err) => eprintln!("Error opening VERSION: {err}"),
        }
    }

    println!("Clearing cache directory...");
    if extract_dir.exists() {
        fs::remove_dir_all(&extract_dir).expect("remove directory");
        fs::create_dir(&extract_dir).expect("create directory");
    }

    println!("Fetching {sdk_version} SDK...");
    let download_path = main_dir.join("fetch.zip");
    reqwest::blocking::get(format!(
        "https://dl-game-sdk.discordapp.net/{sdk_version}/discord_game_sdk.zip"))
        .expect("request Discord Game SDK")
        .copy_to(&mut File::create(&download_path).expect("Discord Game SDK local cache"))
        .expect("download Discord Game SDK");
    ZipArchive::new(File::open(&download_path).expect("read download cache"))
        .expect("valid zip")
        .extract(&extract_dir)
        .expect("extract cached zip");
    fs::remove_file(download_path).expect("remove cached zip");
    File::create(version_file_path).expect("create VERSION")
        .write_all(unsafe { sdk_version.as_bytes_mut() }).expect("write VERSION");
    println!("SDK fetched.");

    // Prepare for library

    fs::rename(extract_dir.join("lib/x86_64/discord_game_sdk.so"),
               extract_dir.join("lib/x86_64/libdiscord_game_sdk.so")).unwrap();
    fs::rename(extract_dir.join("lib/x86_64/discord_game_sdk.dylib"),
               extract_dir.join("lib/x86_64/libdiscord_game_sdk.dylib")).unwrap();
    fs::rename(extract_dir.join("lib/x86_64/discord_game_sdk.dll.lib"),
               extract_dir.join("lib/x86_64/discord_game_sdk.lib")).unwrap();
    fs::rename(extract_dir.join("lib/x86/discord_game_sdk.dll.lib"),
               extract_dir.join("lib/x86/discord_game_sdk.lib")).unwrap();

    extract_dir
}

fn verify_installation(target: &str, sdk_path: &Path) {
    match target {
        "x86_64-unknown-linux-gnu" => {
            assert!(
                sdk_path.join("lib/x86_64/libdiscord_game_sdk.so").exists(),
                "{}",
                MISSING_SETUP
            );
        }

        "x86_64-apple-darwin" => {
            assert!(
                sdk_path
                    .join("lib/x86_64/libdiscord_game_sdk.dylib")
                    .exists(),
                "{}",
                MISSING_SETUP
            );
        }

        "x86_64-pc-windows-gnu" | "x86_64-pc-windows-msvc" => {
            assert!(
                sdk_path.join("lib/x86_64/discord_game_sdk.lib").exists(),
                "{}",
                MISSING_SETUP
            );
        }

        "i686-pc-windows-gnu" | "i686-pc-windows-msvc" => {
            assert!(
                sdk_path.join("lib/x86/discord_game_sdk.lib").exists(),
                "{}",
                MISSING_SETUP
            );
        }

        _ => panic!("{}", INCOMPATIBLE_PLATFORM),
    }
}

fn configure_linkage(target: &str, sdk_path: &Path) {
    match target {
        "x86_64-unknown-linux-gnu"
        | "x86_64-apple-darwin"
        | "x86_64-pc-windows-gnu"
        | "x86_64-pc-windows-msvc" => {
            println!("cargo:rustc-link-lib=discord_game_sdk");
            println!(
                "cargo:rustc-link-search={}",
                sdk_path.join("lib/x86_64").display()
            );
        }

        "i686-pc-windows-gnu" | "i686-pc-windows-msvc" => {
            println!("cargo:rustc-link-lib=discord_game_sdk");
            println!(
                "cargo:rustc-link-search={}",
                sdk_path.join("lib/x86").display()
            );
        }

        _ => {}
    }
}

fn generate_ffi_bindings(builder: bindgen::Builder) {
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());

    builder
        .ctypes_prefix("ctypes")
        .derive_copy(true)
        .derive_debug(true)
        .derive_default(true)
        .derive_eq(true)
        .derive_hash(true)
        .derive_partialeq(true)
        .generate_comments(false)
        .impl_debug(true)
        .impl_partialeq(true)
        .parse_callbacks(Box::new(Callbacks))
        .prepend_enum_name(false)
        .allowlist_function("Discord.+")
        .allowlist_type("[EI]?Discord.+")
        .allowlist_var("DISCORD_.+")
        .generate()
        .expect("discord_game_sdk_sys: bindgen could not generate bindings")
        .write_to_file(out_path.join("bindings.rs"))
        .expect("discord_game_sdk_sys: could not write bindings to file");
}

#[derive(Debug)]
struct Callbacks;

impl bindgen::callbacks::ParseCallbacks for Callbacks {
    fn int_macro(&self, name: &str, _value: i64) -> Option<bindgen::callbacks::IntKind> {
        // Must match sys::DiscordVersion
        if name.ends_with("_VERSION") {
            Some(bindgen::callbacks::IntKind::I32)
        } else {
            None
        }
    }
}

const MISSING_SETUP: &str = r#"

discord_game_sdk_sys: Hello,

You are trying to link to the Discord Game SDK.
Some additional set-up is required, namely some files need to be copied for the linker:

# Linux: prepend with `lib` and add to library search path
$ cp $DISCORD_GAME_SDK_PATH/lib/x86_64/{,lib}discord_game_sdk.so
$ export LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}$DISCORD_GAME_SDK_PATH/lib/x86_64

# Mac OS: prepend with `lib` and add to library search path
$ cp $DISCORD_GAME_SDK_PATH/lib/x86_64/{,lib}discord_game_sdk.dylib
$ export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH:+${DYLD_LIBRARY_PATH}:}$DISCORD_GAME_SDK_PATH/lib/x86_64

# Windows: copy `*.dll.lib` to `*.lib` (won't affect library search)
$ cp $DISCORD_GAME_SDK_PATH/lib/x86_64/discord_game_sdk.{dll.lib,lib}
$ cp $DISCORD_GAME_SDK_PATH/lib/x86/discord_game_sdk.{dll.lib,lib}

After all this, `cargo build` and `cargo run` should function as expected.

Please report any issues you have at:
https://github.com/ldesgoui/discord_game_sdk

Thanks, and apologies for the inconvenience

"#;

const INCOMPATIBLE_PLATFORM: &str = r#"

discord_game_sdk_sys: Hello,

You are trying to link to the Discord Game SDK.
Unfortunately, the platform you are trying to target is not supported.

Please report any issues you have at:
https://github.com/ldesgoui/discord_game_sdk

Thanks, and apologies for the inconvenience

"#;

And discord_game_sdk_sys/Cargo.toml:

[build-dependencies]
bindgen = { version = "0.69.4", default-features = false, features = ["runtime"] }
reqwest = { version = "0.12.5", features = ["blocking"] }
zip = "2.1.3"
bjorn3 commented 5 months ago

The Discord GameSDK uses Rust too internally and exports rust_eh_personality for whatever reason. This rust_eh_personality conflicts with the one from the libstd copy used by your own program. Discord will have to fix their SDK to stop exporting rust_eh_personality. A normal cdylib build doesn't export rust_eh_personality. Without help from someone working at Discord it would be very hard to figure out why rust_eh_personality gets exported by their SDK.

BenCheung0422 commented 5 months ago

Oh I see, so their SDK cannot be used in Rust as library at the moment. Perhaps this issue can be filed to Discord then?

bjorn3 commented 5 months ago

Yeah, that would be your best bet. Until they have fixed it, maybe you could try somehow patching the import library to remove the rust_eh_personality export?

BenCheung0422 commented 5 months ago

It can compile when I used

lib /MACHINE:X86 /def:discord_game_sdk.def /out:discord_game_sdk.lib

with

LIBRARY DISCORD_GAME_SDK
EXPORTS
    DiscordCreate  @1
    DiscordVersion @2

Reference: https://stackoverflow.com/a/280567