woboq/qmetaobject-rs

qttypes: Support C++ toolchains on Windows other than MSVC

Closed this issue · 2 comments

Summary

Build.rs script of qttypes assumes that on Windows the toolchain is always MSVC, which leads to linkage errors with e.g. MinGW toolchain.

Description

As discussed in #150, there are lines in qttypes/build.rs which only work for MSVC setup:

let windows_dbg_suffix = if debug && cfg!(target_os = "windows") {
println!("cargo:rustc-link-lib=msvcrtd");
"d"
} else {
""
};

It produces this output:

cargo:rustc-link-lib=msvcrtd
cargo:rustc-link-search=C:/Qt/5.15.2/mingw81_32/lib
cargo:rustc-link-lib=Qt5Cored
cargo:rustc-link-lib=Qt5Guid
cargo:rustc-link-lib=Qt5Widgetsd
cargo:rustc-link-lib=Qt5Quickd
cargo:rustc-link-lib=Qt5Qmld

which instructs cargo to pass to a linked a set of libs specifically built for debugging with MSVC toolchain. Libraries with a d suffix are only shipped with Qt MSVC builds.

Note that since there is an if debug variable check involved in condition, this bug does not happen on --release mode.

Also, I don't think msvcrt/msvcrtd is really needed outside of MSVC ecosystem? Sounds like a compiler's runtime library, similar to compiler-rt in LLVM land.

Steps to reproduce

  1. Use Windows
  2. Do NOT install Visual Studio / MSVC toolchain (or at least hide it from env for now)
  3. Install rustup, add GNU target, say x86_64-pc-windows-gnu or i686-pc-windows-gnu. To avoid passing around --target arguments, default target can be saved in Cargo configuration.
  4. Install Qt with online installer. Make sure to add Qt libs and compiler toolchain built with and for MinGW, matching bitness or Cargo target.
  5. Cargo-build qmetaobject-rs itself or as a part of another project in debug mode (i.e. NOT --release).

Expected result

Everything should be OK.

Actual result

  = note: C:/Qt/Tools/mingw810_32/bin/../lib/gcc/i686-w64-mingw32/8.1.0/../../../../i686-w64-mingw32/bin/ld.exe: cannot find -lmsvcrtd
          C:/Qt/Tools/mingw810_32/bin/../lib/gcc/i686-w64-mingw32/8.1.0/../../../../i686-w64-mingw32/bin/ld.exe: cannot find -lQt5Cored
          C:/Qt/Tools/mingw810_32/bin/../lib/gcc/i686-w64-mingw32/8.1.0/../../../../i686-w64-mingw32/bin/ld.exe: cannot find -lQt5Guid
          C:/Qt/Tools/mingw810_32/bin/../lib/gcc/i686-w64-mingw32/8.1.0/../../../../i686-w64-mingw32/bin/ld.exe: cannot find -lQt5Widgetsd
          C:/Qt/Tools/mingw810_32/bin/../lib/gcc/i686-w64-mingw32/8.1.0/../../../../i686-w64-mingw32/bin/ld.exe: cannot find -lQt5Quickd
          C:/Qt/Tools/mingw810_32/bin/../lib/gcc/i686-w64-mingw32/8.1.0/../../../../i686-w64-mingw32/bin/ld.exe: cannot find -lQt5Qmld
          collect2.exe: error: ld returned 1 exit status

Now what?

Add to qttypes build script a check for current toolchain being used, and only add compiler runtime and d suffix if target is related to MSCV.

Version

My best guess is qmetaobject-rs starting from v0.2. It doesn't happen on published v0.1.4, i.e. before factoring out qttypes into its own crate.

No good. It can be set to "gnu", even though everything else is MSVC, including cargo:rustc-link-search=C:/Qt/5.15.2/msvc2019/lib. Looks like its value is taken from toolchain, not host nor the target.

This is my setup right now:

  • PATH points to C:/Qt/5.15.2/msvc2019/{lib,bin}
  • Windows 10 SDK and C++ build tools installed (rust-lang/rustup#2809)
  • rustup:
    • Default host: x86_64-pc-windows-msvc
    • toolchain: stable-x86_64-pc-windows-gnu (default)
    • target: i686-pc-windows-msvc
  • Settings up dev env by executing these lines in cmd.exe consecutively:
    • "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars32.bat"
    • "C:\Qt\5.15.2\msvc2019\bin\qtenv2.bat"
    • "C:\Program Files\PowerShell\7\pwsh.exe"
    • (needless to say, I'm launching new Windows Terminal with PowerShell profile just to type cmd, paste those lines and dive into another PowerShell session)

As you can see, I've been through some MS s**t this weekend. But at least certainly learned something new...

Still, qttypes build script produces this output:

TARGET = Some("i686-pc-windows-msvc")
HOST = Some("x86_64-pc-windows-gnu")
DEBUG = Some("true")
running: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC\\14.29.30037\\bin\\HostX64\\x86\\cl.exe" "-nologo" "-MD" "-Z7" "-Brepro" "-I" "C:\\Users\\ratijas\\projects\\qmetaobject-rs\\qttypes" "-I" "C:/Qt/5.15.2/msvc2019/include" "-W4" "-FoC:\\Users\\ratijas\\projects\\qmetaobject-rs\\target\\i686-pc-windows-msvc\\debug\\build\\qttypes-6b4ec5005a66d071\\out\\rust_cpp\\cpp_closures.o" "-c" "C:\\Users\\ratijas\\projects\\qmetaobject-rs\\target\\i686-pc-windows-msvc\\debug\\build\\qttypes-6b4ec5005a66d071\\out\\rust_cpp\\cpp_closures.cpp"
cpp_closures.cpp
running: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC\\14.29.30037\\bin\\HostX64\\x86\\lib.exe" "-out:C:\\Users\\ratijas\\projects\\qmetaobject-rs\\target\\i686-pc-windows-msvc\\debug\\build\\qttypes-6b4ec5005a66d071\\out\\librust_cpp_generated.a" "-nologo" "C:\\Users\\ratijas\\projects\\qmetaobject-rs\\target\\i686-pc-windows-msvc\\debug\\build\\qttypes-6b4ec5005a66d071\\out\\rust_cpp\\cpp_closures.o"
exit code: 0
cargo:rustc-link-lib=static=rust_cpp_generated
cargo:rustc-link-search=native=C:\Users\ratijas\projects\qmetaobject-rs\target\i686-pc-windows-msvc\debug\build\qttypes-6b4ec5005a66d071\out
cargo:VERSION=5.15.2
cargo:LIBRARY_PATH=C:/Qt/5.15.2/msvc2019/lib
cargo:INCLUDE_PATH=C:/Qt/5.15.2/msvc2019/include
cargo:FOUND=1
cargo:rustc-link-search=C:/Qt/5.15.2/msvc2019/lib
cargo:rustc-link-lib=Qt5Core
cargo:rustc-link-lib=Qt5Gui
cargo:rustc-link-lib=Qt5Widgets
cargo:rustc-link-lib=Qt5Quick
cargo:rustc-link-lib=Qt5Qml
cargo:rustc-link-lib=Qt5WebEngine
cargo:rerun-if-changed=src/lib.rs

Not bad, and even compiles and runs. But is definitely not what we expected. I print-debugged the cfg!(target_env = "...") value, and turned out it is really set to "gnu". Uhh...

Oh, wait, i know why! It's because build scripts are special. They are being built as a binaries for the target platform of a host. Hence the target env matches my toolchain. Easy.

What we need instead are Environment variables Cargo sets for build scripts, namely CARGO_CFG_TARGET_ENV.

And... boom! Tetris for Jeff. We got our d's back — but only when they're needed:

TARGET = Some("i686-pc-windows-msvc")
HOST = Some("x86_64-pc-windows-gnu")
DEBUG = Some("true")
cargo:VERSION=5.15.2
cargo:LIBRARY_PATH=C:/Qt/5.15.2/msvc2019/lib
cargo:INCLUDE_PATH=C:/Qt/5.15.2/msvc2019/include
cargo:FOUND=1
cargo:rustc-link-lib=msvcrtd
cargo:rustc-link-search=C:/Qt/5.15.2/msvc2019/lib
cargo:rustc-link-lib=Qt5Cored
cargo:rustc-link-lib=Qt5Guid
...

Todo example with debug libs