├── .gitattributes ├── .gitignore ├── src └── main.rs ├── Cargo.lock ├── Cargo.toml ├── .cargo └── config.toml ├── ci ├── build.ps1 ├── setup.ps1 └── common.ps1 ├── appveyor.yml ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, World!"); 3 | } 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "cross-compile-sample" 3 | version = "0.0.0" 4 | 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cross-compile-sample" 3 | version = "0.0.0" 4 | authors = ["Ashley Mannix "] 5 | edition = "2018" 6 | publish = false 7 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-unknown-linux-musl] 2 | linker = "rust-lld" 3 | 4 | [target.aarch64-unknown-linux-musl] 5 | linker = "rust-lld" 6 | 7 | [target.x86_64-pc-windows-msvc] 8 | rustflags = ["-C", "target-feature=+crt-static"] 9 | -------------------------------------------------------------------------------- /ci/build.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | Push-Location "$PSScriptRoot/../" 4 | 5 | . "./ci/common.ps1" 6 | 7 | Run-Command -Exe cargo -ArgumentList 'build', '--target x86_64-pc-windows-msvc' 8 | Run-Command -Exe cargo -ArgumentList 'build', '--target x86_64-unknown-linux-musl' 9 | Run-Command -Exe cargo -ArgumentList 'build', '--target aarch64-unknown-linux-musl' 10 | 11 | Pop-Location 12 | -------------------------------------------------------------------------------- /ci/setup.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | Push-Location "$PSScriptRoot/../" 4 | 5 | . "./ci/common.ps1" 6 | 7 | Invoke-WebRequest -OutFile ./rustup-init.exe -Uri https://win.rustup.rs 8 | 9 | Run-Command -Exe ./rustup-init.exe -ArgumentList ` 10 | "--default-host x86_64-pc-windows-msvc", ` 11 | "--default-toolchain $env:RUST_TOOLCHAIN", ` 12 | "-y" 13 | 14 | $env:Path = "C:\Users\appveyor\.cargo\bin;$env:Path" 15 | 16 | Run-Command -Exe rustup -ArgumentList "target", "add", "x86_64-unknown-linux-musl" 17 | Run-Command -Exe rustup -ArgumentList "target", "add", "aarch64-unknown-linux-musl" 18 | 19 | Pop-Location 20 | -------------------------------------------------------------------------------- /ci/common.ps1: -------------------------------------------------------------------------------- 1 | function Run-Command 2 | { 3 | Param ($Exe, $ArgumentList) 4 | 5 | # For commands that treat stderr like stdetc 6 | $out = New-TemporaryFile 7 | $err = New-TemporaryFile 8 | $r = Start-Process $Exe -ArgumentList $ArgumentList -Wait -PassThru -RedirectStandardOut $out.FullName -RedirectStandardError $err.FullName 9 | 10 | Write-Output "STDOUT" 11 | Get-Content -Path $out.FullName 12 | Write-Output "" 13 | 14 | Write-Output "STDERR" 15 | Get-Content -Path $err.FullName 16 | Write-Output "" 17 | 18 | Remove-Item $out.FullName 19 | Remove-Item $err.FullName 20 | 21 | if ($r.ExitCode -ne 0) { 22 | exit $r.ExitCode 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '0.0.{build}' 2 | skip_tags: true 3 | image: 4 | - Visual Studio 2017 5 | test: off 6 | 7 | environment: 8 | RUST_TOOLCHAIN: 'stable' 9 | 10 | init: 11 | - git config --global core.longpaths true 12 | 13 | install: 14 | - ps: .\ci\setup.ps1 15 | - ps: $env:Path = "C:\Users\appveyor\.cargo\bin;$env:Path" 16 | 17 | build_script: 18 | - ps: .\ci\build.ps1 19 | 20 | artifacts: 21 | - path: target\x86_64-pc-windows-msvc\debug\cross-compile-sample.exe 22 | name: Windows Binary 23 | 24 | - path: target\x86_64-unknown-linux-musl\debug\cross-compile-sample 25 | name: Linux x86 Binary 26 | 27 | - path: target\aarch64-unknown-linux-musl\debug\cross-compile-sample 28 | name: Linux ARM Binary 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ashley Mannix 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Statically linking and cross-compiling Rust apps on Windows [![Build status](https://ci.appveyor.com/api/projects/status/ku7y70c7jwp8x7i8?svg=true)](https://ci.appveyor.com/project/KodrAus/cross-compile-example) 2 | 3 | This example demonstrates how you can use just the tools that are readily accessible through [`rustup`](https://rustup.rs) to statically link and cross-compile Rust apps on Windows. 4 | 5 | ----- 6 | 7 | Rust is compiled ahead-of-time to machine code that runs directly on an end-user's machine. That means you have to know upfront what platforms you're going to target and have the right build tools and libraries available for each of them. 8 | 9 | Even when you do have compiled binaries, you can run into problems distributing them if you find yourself depending on the availability of C runtime libraries on the end-user's machine. 10 | 11 | When your build environment is Windows and you also need to target Linux, it turns out we can solve both our cross-compilation and distribution problems at once by statically linking MSVCRT for Windows and by cross-compiling our Linux builds to target musl instead of glibc. 12 | 13 | Let's start with a fresh _Hello World_ Rust app: 14 | 15 | ```shell 16 | cargo new --bin cross-compile-sample 17 | cd cross-compile-sample 18 | ``` 19 | 20 | ## Statically linking MSVCRT 21 | 22 | If we build our library for the MSVC target (which is the default for Windows) now, it'll dynamically link to MSVCRT: 23 | 24 | ```shell 25 | cargo build --target x86_64-pc-windows-msvc 26 | ``` 27 | 28 | ```shell 29 | ls target/x86_64-pc-windows-msvc/debug 30 | ``` 31 | 32 | ```shell 33 | Length Name 34 | ------ ---- 35 | 144384 cross-compile-sample.exe 36 | ``` 37 | 38 | In order to tell the Rust compiler to statically link MSVCRT, we need to add some configuration to a `.cargo/config.toml` file: 39 | 40 | ```toml 41 | [target.x86_64-pc-windows-msvc] 42 | rustflags = ["-C", "target-feature=+crt-static"] 43 | ``` 44 | 45 | The [`crt-static` target feature](https://github.com/rust-lang/rfcs/blob/master/text/1721-crt-static.md) is a code generation option that's only available for targets that are suitable for both static and dynamic linkage. MSVC is one of those targets. When `crt-static` is specified, the C runtime libraries will be linked statically instead of dynamically. 46 | 47 | Building our app again results in a different binary: 48 | 49 | ```shell 50 | cargo build --target x86_64-pc-windows-msvc 51 | ``` 52 | 53 | ```shell 54 | ls target/x86_64-pc-windows-msvc/debug 55 | ``` 56 | 57 | ```shell 58 | Length Name 59 | ------ ---- 60 | 241152 cross-compile-sample.exe 61 | ``` 62 | 63 | It's bigger than before because we have the relevant pieces of MSVCRT included. 64 | 65 | ## Cross-compiling to Linux 66 | 67 | For Windows, we statically link MSVCRT because it's more convenient for end-users. The `crt-static` feature solves our distribution problem. For Linux, we're going to statically link the [musl](https://www.musl-libc.org/intro.html) libc because it's more convenient for us at build time (and is more portable). musl is a complete, self-contained Linux libc with no system dependencies. That means we don't have to provide Linux system libraries to dynamically link to in our Windows build environment. musl solves our cross-compilation problem. 68 | 69 | We can install musl as a target for Rust using `rustup`: 70 | 71 | ```shell 72 | rustup target add x86_64-unknown-linux-musl 73 | ``` 74 | 75 | Attempting to build right now probably won't work though: 76 | 77 | ```shell 78 | cargo build --target x86_64-unknown-linux-musl 79 | ``` 80 | 81 | ```shell 82 | error: linker `cc` not found 83 | error: could not compile `cross-compile-sample` 84 | ``` 85 | 86 | We've got the runtime we need, but not the build tools to link up our final Linux binary. Well, actually we do have the build tools we need. We're just not using them yet. Rust [embeds LLVM's linker](https://github.com/rust-lang/rust/issues/39915), `lld`, which we can use instead of the unavailable `cc` to link our Linux binary on Windows. 87 | 88 | Adding `rust-lld` as the linker for our musl target in our `.cargo/config.toml` file will switch from `cc` to Rust's `lld`: 89 | 90 | ```toml 91 | [target.x86_64-unknown-linux-musl] 92 | linker = "rust-lld" 93 | ``` 94 | 95 | We should now be able to cross-compile a Linux binary from our Windows host: 96 | 97 | ```shell 98 | cargo build --target x86_64-unknown-linux-musl 99 | ``` 100 | 101 | ```shell 102 | ls target/x86_64-unknown-linux-musl/debug 103 | ``` 104 | 105 | ```shell 106 | Length Name 107 | ------ ---- 108 | 3041624 cross-compile-sample 109 | ``` 110 | 111 | ### Limitations 112 | 113 | - You can't directly or transitively depend on any libraries that need to compile C code. That includes the `failure` crate with its `backtrace` dependency. You can build some reasonably complex projects though, including [this UDP server](https://github.com/datalust/sqelf) that depends on `tokio`. 114 | - musl binaries linked using LLD don't seem to be able to recover from panics (there's been [problems with LLVM's `libunwind` port that's used in Rust's musl target in the past](https://github.com/rust-lang/rust/issues/35599)). 115 | 116 | ### Other approaches for cross-compilation 117 | 118 | This example uses a combination of musl and LLD to cross-compile a Linux binary from Windows without needing any tools that aren't readily available through `rustup`. Other approaches include: 119 | 120 | - Use [LCOW](https://docs.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/linux-containers) to build your Linux binaries natively. 121 | - Use a separate Linux build agent. 122 | 123 | They're probably more robust, but depend on the availability of those features, or additional build complexity to coordinate the bundling of artifacts produced in separate environments. Azure Pipelines makes this coordination fairly straightforward though. I've got an example of building a native library (in this case LLVM itself) in Azure Pipelines on several platforms and packaging their artifacts together at the end [here](https://github.com/KodrAus/libllvm). 124 | --------------------------------------------------------------------------------