Support cross compilation from Linux to Windows
Description
Based on #3100 and #3116, here is an dirty implementation of cross compilation for Windows (tested from a Debian 12).
I chose to base my work on v20 branch to make sure I have a stable environment, which allow me to excursively focus on the build system.
Also, the build.rs files don't seem to be very cross-compilation friendly. Since I don't have a good knowledge of alvr build system, I decided to implement an alternative codepath with a lot of code duplication, just to create a proof of concept that can be a base for discussion. I'm pretty sure I missed a lot of details, but at least I managed to have something working.
At the very end, I'm able to build with this simple command:
cargo xtask build-streamer --release windows
It requires some environment variables at first (more details bellow), but I managed to plug it in the existing command flow.
Known limitations
-
build.rsfor Dashboard hasn't been updated. Which means that the executable doesn't have its icon correctly configured. - No ffmpeg build for now. So no x264 support
-
packagecommand not patched
Environment requirements
- Rust target
x86_64-pc-windows-msvcinstalled -
clang-clas a toolchain since it is compilant with Windows C++ ABI - https://github.com/mstorsjo/msvc-wine to get the required MSVC/Windows SDK files
How to build (only tested on Debian 12)
Install required Windows components
Where: in msvc-wine folder.
VS_ROOT_DIR=<msvcinstallpath>
./vsdownload.py \
--accept-license \
--dest $VS_ROOT_DIR \
--host-arch x64 \
Microsoft.VisualStudio.Component.VC.14.44.17.14.x86.x64 \
Microsoft.Component.VC.Runtime.UCRTSDK \
Microsoft.VisualStudio.Component.VC.Redist.14.Latest \
Microsoft.VisualStudio.Component.Windows11SDK.26100
./install.sh $VS_ROOT_DIR
Note this may be improved, I discovered msvc-wine while working on this PR.
Setup build env
VS_ROOT_DIR=<msvcinstallpath>
export CXX=clang-cl-19
export AR=llvm-lib-19
export TARGET_CC=clang-cl-19
export CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER=lld-link-19
MSVC_ROOT=$VS_ROOT_DIR/vc/tools/msvc/14.44.35207
WINSDK_INCLUDE=$VS_ROOT_DIR/kits/10/include/10.0.26100.0
WINSDK_LIB=$VS_ROOT_DIR/kits/10/lib/10.0.26100.0
export INCLUDE="$MSVC_ROOT/include;$WINSDK_INCLUDE/shared;$WINSDK_INCLUDE/um;$WINSDK_INCLUDE/ucrt;$WINSDK_INCLUDE/winrt"
export LIB="$MSVC_ROOT/lib/x64;$WINSDK_LIB/ucrt/x64;$WINSDK_LIB/um/x64"
Build
cargo xtask build-streamer --release windows
Output
build
└── alvr_streamer_windows
├── ALVR Dashboard.exe
├── bin
│ └── win64
│ ├── alvr_server_openvr.pdb
│ ├── driver_alvr_server.dll
│ ├── openvr_api.dll
│ └── vcruntime140_1.dll
└── driver.vrdrivermanifest
making use of
std::env:consts::OS, which should point to the destination compilation target, not the host
I'm not familiar with this part of Rust, but actually this seems not to be true in build.rs. As it is an host tool, its target is the host OS, not the --target specified on cargo call. Which mean that cfg!(...) have an "unexpected" behavior. I attached a code example bellow.
I should have been more explicit about this part in the MR description. This is why I chose to create an alternate codepath to make it work, since I think some preliminary changes in the build system could be required at first. During my first try to add cross compilation support, I was triggering all the Linux build codepath, and I want tweaking all the conditional build tests, which lead to heavy changes.
You should rebase your branch because some changes were already merged.
I can open a new PR targeting master (since the name of my current branch is explicit about v20.14.1), but I suggest we can discuss about the correct strategy to remove code duplication. Then I'll be able to provide a PR targeting master, with a correct implementation.
build.rs behavior tests
build.rs example
fn main() {
println!("cargo:warning=In build.rs");
println!("cargo:warning=std::env:consts::OS={}", std::env::consts::OS);
if cfg!(windows) {
println!("cargo:warning=cfg!(windows)");
} else if cfg!(target_os = "linux") {
println!("cargo:warning=cfg!(target_os = \"linux\")");
}
}
Linux build
$ cargo build
warning: [email protected]: In build.rs
warning: [email protected]: std::env:consts::OS=linux
warning: [email protected]: cfg!(target_os = "linux")
Linux -> Windows build
$ cargo build --target "x86_64-pc-windows-msvc"
Compiling rusttest v0.1.0 (/mnt/kyber/rusttest)
warning: [email protected]: In build.rs
warning: [email protected]: std::env:consts::OS=linux
warning: [email protected]: cfg!(target_os = "linux")
Ah I see, so env::var("CARGO_CFG_TARGET_OS") is the only good way to do this. I would say that interleaving the code is not that bad, as I see that significant portions of the code are duplicated. you can use many small if-else wherever you need instead.
You'll have to rebase this on top of master so that we can actually do anything with it, tho I can do that for you if you think that's too much for you.
It seems I also have to integrate #3051 since it adds an extra dependency, which is cross compiled too. I'll be busy the next week, I'm not sure I can provide a rebase quickly unfortunately.