I686 pc windows gnu

The Rust RFC Book

  • Feature Name: none
  • Start Date: 2025-02-11
  • RFC PR: rust-lang/rfcs#3771
  • Rust Issue: rust-lang/rust#0000

Summary

Demote target i686-pc-windows-gnu from Tier 1 to Tier 2 (with host tools) to better reflect its current maintenance and usage status.

Motivation

Background

Rust has supported Windows for a long time, with two different flavors of Windows targets: MSVC-based and GNU-based.
MSVC-based targets (for example the main Windows target x86_64-pc-windows-msvc) use Microsoft’s native link.exe linker and libraries, while GNU-based targets (like i686-pc-windows-gnu) are built entirely from free software components like gcc, ld, and MinGW.

The major reason to use a GNU-based toolchain instead of the native MSVC-based one is cross-compilation and licensing. link.exe only runs on Windows (barring Wine hacks) and requires a license for commercial usage.

Rust currently supports the following major Windows targets. They all have host tools. The download count was extracted from the public dashboard on 2025-02-10.
We also show the std download counts to account for cross-compilation usage.

Name Tier rustc download count std download count
x86_64-pc-windows-msvc 1 6.72M 3.56M
x86_64-pc-windows-gnu 1 375K 1.06M
i686-pc-windows-msvc 1 260k 793K
i686-pc-windows-gnu 1 76K 56K
aarch64-pc-windows-msvc 2 46K 241K

To put the download numbers into perspective, some other targets:

Name Tier rustc download count std download count
x86_64-unknown-linux-gnu 1 135M 65M
i686-unknown-linux-gnu 1 332K 437K
x86_64-unknown-freebsd 2 138K 89K
x86_64-unknown-netbsd 2 36K 32K

From the download count alone, i686-pc-windows-gnu better fits in next to other Tier 2 targets like FreeBSD and NetBSD.

But that is not everything. GNU-based Windows targets are, as the description at the start may imply, an alternative (you could say non-standard) way to compile for Windows, and as such subject to many kinds of unique problems.
The Rust Project currently does not have a lot of expertise for dealing with these issues.
The setup and build for Windows GNU is complicated and prone to errors, often failing in CI, leading to frequent efforts to fix them being carried out by people who are, at best, familiar with 64-bit Windows or Windows MSVC.
This results in Windows-GNU problems often being unaddressed, or worse: fixed in ways that turn up more errors later down the line.

Some example problems, found by searching for ignore-windows-gnu in rust-lang/rust.

  • https://github.com/rust-lang/rust/issues/128973
  • https://github.com/rust-lang/rust/issues/128981
  • https://github.com/rust-lang/rust/pull/116837
  • https://github.com/rust-lang/rust/issues/128911

32-bit x86 Problems

While some of these issues apply to all GNU-based targets, 32-bit x86 seems to be especially affected.
And when a 32-bit Windows GNU issue comes up, contributors rarely actually investigate it, because it is such a complex and nonstandard environment compared to 64-bit Windows GNU, which is a lot easier to set up and work with.

That the 32-bit x86 architecture is unusual, and made moreso by how Windows operates on it, has also been noted by Windows experts1.
The Windows GNU experts that provide direct support to the Rust project focus almost exclusively on the 64-bit targets, and have previously recommended the retirement of the 32-bit targets2.

MSYS2, a major distributor of the GNU-based Windows platform, has been dropping some 32-bit packages and no longer distributes Clang for 32-bit, showing even their shift away from the platform.
In response to inquiries about their opinion on reducing support for the target, MSYS2 folks were positive.

Target Tier Policy Requirements

With this knowledge, we can look at the Tier 1 requirements of the target tier policy to check whether they are fulfilled.

Tier 1 targets must have substantial, widespread interest within the developer community, and must serve the ongoing needs of multiple production users of Rust across multiple organizations or projects.

While this cannot be quantified precisely, the download counts suggest that this target is less popular than some other Tier 2 targets like FreeBSD.
Therefore, we are going to treat this as false.

The target maintainer team must include at least 3 developers.

This is not the case at all. There is currently no maintainer team.
Though we should note that this is currently also true for many other Tier 1 targets, as this is a new rule not upheld everywhere yet.
But experience tells that it is highly unlikely that 3 maintainers for 32 bit Windows GNU will be found.

The target must build and pass tests reliably in CI, for all components that Rust’s CI considers mandatory.

As mentioned above, there are issues and it does cause a fair share of problems.

The target must not disable an excessive number of tests or pieces of tests in the testsuite in order to do so. This is a subjective requirement.

A fair amount of tests are disabled with an open issue with no comments.
I would say that it is on the edge of being excessive, not quite having reached that amount (but it is likely that will be reached eventually).
For example, #134777 observed and un-ignored a lot of ignored Windows tests, many of which were likely ignored on all of Windows because of Windows GNU issues.
Another example of an ignored test is #135572 that does not support Windows GNU because it was too mcuh effort to test locally.

The target must provide as much of the Rust standard library as is feasible and appropriate to provide […].

Windows is well-supported in the standard library.

Building the target and running the testsuite for the target must not take substantially longer than other targets

Building i686-pc-windows-gnu is reasonably fast.

If running the testsuite requires additional infrastructure

GitHub Actions has Windows support, which is used for i686-pc-windows-gnu (on a 64-bit host), no external infrastructure is required.

Tier 1 targets must not have a hard requirement for signed, verified, or otherwise “approved” binaries.

There are no such requirements.

All requirements for tier 2 apply.

We will not go through Tier 2 requirements here, but they are, apart from the (less strict than Tier 1) maintainer requirements, fulfilled.
When the maintainer requirements are enforced more strictly in the future, i686-pc-windows-gnu (and x86_64-pc-windows-gnu as well) may be demoted further if no maintainers are found.

Conclusion

Given the usage count and lack of maintenance leading to more than one requirement not being fulfilled, it becomes clear that i686-pc-windows-gnu is not worthy of being a Tier 1 target and is already getting much worse support than expected from a Tier 1 target.

Explanation

i686-pc-windows-gnu is now a Tier 2 with Host Tools target instead of a Tier 1 With Host Tools target.
Official builds of the standard library and rustc continue to be distributed for this target, but it is no longer tested in CI.
If necessary, further demotions (for example removing host tools) will not require RFCs, but go through a simpler MCP instead.

A blog post will be made to describe the change.

Drawbacks

By no longer doing automated testing for this target, this target will likely deteriorate more quickly than with continued automated testing.

Additionally, this opens the door for further demotions in the future, like removing host tools, which could still be useful to some people.
But such demotions will always have to be justified on their own.

Rationale and alternatives

The maintenance requirement violation can be solved by multiple people stepping up to maintain this target. This has not happened so far.

The popularity requirement could be fulfilled by more people using this target, but this does not seem possible, as 32-bit x86 as been on a decline for a long time, as new CPU models for this architecture are no longer being made.

Prior art

This is the first time since the Target Tier Policy was created (note that this links to an old version, see the rustc book for the latest version at the time of writing) that a Tier 1 target is being demoted.

Before that, there has been the demotion of i686-apple-darwin from Tier 1 to Tier 3 in 2019.
The reasoning there was mostly Apple’s support being removed, which is not the case here.
The measures in this RFC are much less drastic.

The promotion of aarch64-apple-darwin to Tier 1 cited popularity as the major motivation, matching unpopularity as one of the motivations here.

This is a continuation of MCP 822, which contains some additional details in the description and linked Zulip stream.

Unresolved questions

None so far.

Future possibilities

x86_64-pc-windows-gnu will remain a Tier 1 target after this RFC.
While its popularity is more aligned with Tier 1, it suffers from the same lack of maintenance (but to a lesser degree) as its 32-bit cousin.
It may be demoted as well in the future.

i686-pc-windows-gnu may be demoted to a Tier 2 target without host tools in the future if it is not deemed useful enough.
This will likely happen in the near future, but is not part of this RFC.
That demotion will not need an RFC.

The *-windows-gnullvm targets, which are based on LLVM instead of GNU tools, may see increased maintenance and popularity in the future, replacing the *-windows-gnu targets.
But it seems unlikely that i686-pc-windows-gnullvm would ever acquire Tier 1 status.


  1. https://devblogs.microsoft.com/oldnewthing/20220418-00/?p=106489 ↩

  2. despite saying he is only a maintainer for x86_64-pc-windows-gnullvm, mati865 is effectively also our maintainer for x86_64-pc-windows-gnu https://rust-lang.zulipchat.com/#narrow/channel/233931-t-compiler.2Fmajor-changes/topic/Demote.20.60i686-pc-windows-gnu.60.20compiler-team.23822/near/490675824 ↩

4 releases

Uses old Rust 2015

0.4.0 Jan 18, 2018
0.3.2 Dec 28, 2017
0.3.1 Mar 14, 2017
0.3.0 Nov 23, 2016

#280 in Windows APIs

Download history

1452883/week @ 2024-12-02
1459573/week @ 2024-12-09
1444180/week @ 2024-12-16
599019/week @ 2024-12-23
677820/week @ 2024-12-30
1229976/week @ 2025-01-06
1362659/week @ 2025-01-13
1336757/week @ 2025-01-20
1722667/week @ 2025-01-27
1520793/week @ 2025-02-03
1791694/week @ 2025-02-10
1692869/week @ 2025-02-17
1650437/week @ 2025-02-24
1850094/week @ 2025-03-03
1729884/week @ 2025-03-10
1872656/week @ 2025-03-17

7,227,027 downloads per month

Used in 43,577 crates
(3 directly)

MIT/Apache


51MB

Contains
(static library, 1.5MB) lib/libwinapi_ntdll.a,
(static library, 1MB) lib/libwinapi_kernel32.a,
(static library, 640KB) lib/libwinapi_advapi32.a,
(static library, 675KB) lib/libwinapi_icuuc.a,
(static library, 1MB) libwinapi_onecore_downlevel-kernel32.a,
(static library, 1MB) libwinapi_onecoreuap_downlevel-kernel32.a
and 1370 more.

winapi-rs

Documentation

Official communication channel: #windows-dev on the Rust Community Discord

This crate provides raw FFI bindings to all of Windows API. They are gathered by hand using the Windows 10 SDK from Microsoft. I aim to replace all existing Windows FFI in other crates with this crate through the «Embrace, extend, and extinguish» technique.

If this crate is missing something you need, feel free to create an issue, open a pull request, or contact me via other means.

This crate depends on Rust 1.6 or newer on Windows. On other platforms this crate is a no-op and should compile with Rust 1.2 or newer.

Frequently asked questions

How do I create an instance of a union?

Use std::mem::zeroed() to create an instance of the union, and then assign the value you want using one of the variant methods.

Why am I getting errors about unresolved imports?

Each module is gated on a feature flag, so you must enable the appropriate feature to gain access to those items. For example, if you want to use something from winapi::um::winuser you must enable the winuser feature.

How do I know which module an item is defined in?

You can use the search functionality in the documentation to find where items are defined.

Why is there no documentation on how to use anything?

This crate is nothing more than raw bindings to Windows API. If you wish to know how to use the various functionality in Windows API, you can look up the various items on MSDN which is full of detailed documentation.

Can I use this library in no_std projects?

Yes, absolutely! By default the std feature of winapi is disabled, allowing you to write Windows applications using nothing but core and winapi.

Why is winapi‘s HANDLE incompatible with std‘s HANDLE?

Because winapi does not depend on std by default, it has to define c_void itself instead of using std::os::raw::c_void. However, if you enable the std feature of winapi then it will re-export c_void from std and cause winapi‘s HANDLE to be the same type as std‘s HANDLE.

Should I still use those -sys crates such as kernel32-sys?

No. Those crates are a legacy of how winapi 0.2 was organized. Starting with winapi 0.3 all definitions are directly in winapi itself, and so there is no longer any need to use those -sys crates.

Example

Cargo.toml:

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["winuser"] }

main.rs:

#[cfg(windows)] extern crate winapi;
use std::io::Error;

#[cfg(windows)]
fn print_message(msg: &str) -> Result<i32, Error> {
    use std::ffi::OsStr;
    use std::iter::once;
    use std::os::windows::ffi::OsStrExt;
    use std::ptr::null_mut;
    use winapi::um::winuser::{MB_OK, MessageBoxW};
    let wide: Vec<u16> = OsStr::new(msg).encode_wide().chain(once(0)).collect();
    let ret = unsafe {
        MessageBoxW(null_mut(), wide.as_ptr(), wide.as_ptr(), MB_OK)
    };
    if ret == 0 { Err(Error::last_os_error()) }
    else { Ok(ret) }
}
#[cfg(not(windows))]
fn print_message(msg: &str) -> Result<(), Error> {
    println!("{}", msg);
    Ok(())
}
fn main() {
    print_message("Hello, world!").unwrap();
}

Financial Support

Do you use winapi in your projects? If so, you may be interested in financially supporting me on Patreon. Companies in particular are especially encouraged to donate (I’m looking at you Microsoft).

No runtime deps

Provide feedback

Saved searches

Use saved searches to filter your results more quickly

Sign up

Appearance settings

Rust has great support for cross compilation, with
cross, you can install the required c toolchain + linker
and cross compile your rust code to a binary that runs on your targeted
platform. Sweet!

If you’d like to look at the code and results, it’s in this repo
here: https://github.com/Takashiidobe/rust-build-binary-github-actions

Rust library writers use this feature to build and test for other
platforms than their own: hyperfine
for example builds for 11 different platforms.

The rustc
book has a page on targets and tiers of support. Tier 1 supports 8
targets:

Tier 1
aarch64-unknown-linux-gnu
i686-pc-windows-gnu
i686-unknown-linux-gnu
i686-pc-windows-msvc
x86_64-apple-darwin
x86_64-pc-windows-gnu
x86_64-pc-windows-msvc
x86_64-unknown-linux-gnu

Tier 2 with Host tools supports 21 targets.

Tier 2
aarch64-apple-darwin
aarch64-pc-windows-msvc
aarch64-unknown-linux-musl
arm-unknown-linux-gnueabi
arm-unknown-linux-gnueabihf
armv7-unknown-linux-gnueabihf
mips-unknown-linux-gnu
mips64-unknown-linux-gnuabi64
mips64el-unknown-linux-gnuabi64
mipsel-unknown-linux-gnuabi
powerpc-unknown-linux-gnu
powerpc64-unknown-linux-gnu
powerpc64le-unknown-linux-gnu
riscv64gc-unknown-linux-gnu
s390x-unknown-linux-gnu
x86_64-unknown-freebsd
x86_64-unknown-illumos
arm-unknown-linux-musleabihf
i686-unknown-linux-musl
x86_64-unknown-linux-musl
x86_64-unknown-netbsd

Let’s try to build a binary for all 29 targets.

A Note on Targets

The Rust RFC for Target support: https://rust-lang.github.io/rfcs/0131-target-specification.html

A target is defined in three or four parts:

$architecture-$vendor-$os-$environment

The environment is optional, so some targets have three parts and
some have four.

Let’s take x86_64-apple-darwin for example.

  • x86_64 is the architecture
  • apple is the vendor
  • darwin is the os

You’ll notice here that there is no $environment. This
target assumes the environment, which is most likely to be
gnu.

Let’s take one with four parts:
i686-pc-windows-msvc.

  • i686 is the architecture
  • pc is the vendor
  • windows is the os
  • msvc is the environment

In this target, the environment is specified as msvc,
the microsoft C compiler. This is the most popular compiler for windows,
but it need not be: if you look in the same tier 1 table, there’s this
target: i686-pc-windows-gnu.

The only thing that’s changed is the environment is now
gnu. Windows can use gcc instead of
msvc, so building for this target uses the gcc
instead of msvc.

Architectures

Architecture Notes
aarch64 ARM 64 bit
i686 Intel 32 bit
x86_64 Intel 64 bit
arm ARM 32 bit
armv7 ARMv7 32 bit
mips MIPS 32 bit
mips64 MIPS 64 bit
mips64el MIPS 64 bit Little Endian
mipsel MIPS 32 bit Little Endian
powerpc IBM 32 bit
powerpc64 IBM 64 bit
rsicv64gc RISC-V 64 bit
s390x IBM Z 32 bit

Vendors

Vendor Notes
pc Microsoft
apple Apple
unknown Unknown

Operating Systems

Operating System Notes
darwin Apple’s OS
linux Linux OS
windows Microsoft’s OS
freebsd FreeBSD OS
netbsd NetBSD OS
illumos Illumos OS, a Solaris derivative

Environments

Environment Notes
musl Musl C library
gnu GNU’s C library
msvc Microsoft Visual C library
freebsd FreeBSD’s C library
netbsd NetBSD’s C library
illumos Illumos’ C library

When you go to the releases tab to download a particular binary,
you’ll need to know these four things to download a binary that runs on
your system.

Now, let’s start building for all these systems.

Building Binaries for ~30 Targets

We’re going to use Github Actions, a task runner on github.com to
build our binaries. Our binary is a simple hello world
binary.

If you’d just like to look at the github actions file, it’s located
here: https://github.com/Takashiidobe/rust-build-binary-github-actions/blob/master/.github/workflows/release.yml

Conceptually, we’d like to do the following:

  • Set up our target environments.
  • Download the C compiler (environment) we need.
  • Download a docker image of the OS we require.
  • Download the rust toolchain onto docker container.
  • Build the binary.
  • Optionally strip debug symbols.
  • Publish it to the github releases tab.

We’ll first start out by defining our github action and setting up
the target environments:

name: release

env:
  MIN_SUPPORTED_RUST_VERSION: "1.56.0"
  CICD_INTERMEDIATES_DIR: "_cicd-intermediates"

on:
  push:
    tags:
      - '*'

jobs:
  build:
    name: ${{ matrix.job.target }} (${{ matrix.job.os }})
    runs-on: ${{ matrix.job.os }}
    strategy:
      fail-fast: false
      matrix:
        job:
          # Tier 1
          - { target: aarch64-unknown-linux-gnu      , os: ubuntu-20.04, use-cross: true }
          - { target: i686-pc-windows-gnu            , os: windows-2019                  }
          - { target: i686-unknown-linux-gnu         , os: ubuntu-20.04, use-cross: true }
          - { target: i686-pc-windows-msvc           , os: windows-2019                  }
          - { target: x86_64-apple-darwin            , os: macos-10.15                   }
          - { target: x86_64-pc-windows-gnu          , os: windows-2019                  }
          - { target: x86_64-pcwindows-msvc          , os: windows-2019                  }
          - { target: x86_64-unknown-linux-gnu       , os: ubuntu-20.04                  }
          # Tier 2 with Host Tools
          - { target: aarch64-apple-darwin           , os: macos-11.0                    }
          - { target: aarch64-pc-windows-msvc        , os: windows-2019                  }
          - { target: aarch64-unknown-linux-musl     , os: ubuntu-20.04, use-cross: true }
          - { target: arm-unknown-linux-gnueabi      , os: ubuntu-20.04, use-cross: true }
          - { target: arm-unknown-linux-gnueabihf    , os: ubuntu-20.04, use-cross: true }
          - { target: armv7-unknown-linux-gnueabihf  , os: ubuntu-20.04, use-cross: true }
          - { target: mips-unknown-linux-gnu         , os: ubuntu-20.04, use-cross: true }
          - { target: mips64-unknown-linux-gnuabi64  , os: ubuntu-20.04, use-cross: true }
          - { target: mips64el-unknown-linux-gnuabi64, os: ubuntu-20.04, use-cross: true }
          - { target: mipsel-unknown-linux-gnu       , os: ubuntu-20.04, use-cross: true }
          - { target: powerpc-unknown-linux-gnu      , os: ubuntu-20.04, use-cross: true }
          - { target: powerpc64-unknown-linux-gnu    , os: ubuntu-20.04, use-cross: true }
          - { target: powerpc64le-unknown-linux-gnu  , os: ubuntu-20.04, use-cross: true }
          - { target: riscv64gc-unknown-linux-gnu    , os: ubuntu-20.04, use-cross: true }
          - { target: s390x-unknown-linux-gnu        , os: ubuntu-20.04, use-cross: true }
          - { target: x86_64-unknown-freebsd         , os: ubuntu-20.04, use-cross: true }
          - { target: x86_64-unknown-illumos         , os: ubuntu-20.04, use-cross: true }
          - { target: arm-unknown-linux-musleabihf   , os: ubuntu-20.04, use-cross: true }
          - { target: i686-unknown-linux-musl        , os: ubuntu-20.04, use-cross: true }
          - { target: x86_64-unknown-linux-musl      , os: ubuntu-20.04, use-cross: true }
          - { target: x86_64-unknown-netbsd          , os: ubuntu-20.04, use-cross: true }

Checking out our code:

    steps:
    - name: Checkout source code
      uses: actions/checkout@v2

Downloading the C compiler

Most of the time, the C compiler we need is already installed, but in
some cases it’ll be overriden by another compiler.

We’ll need to download the correct suitable C compiler in that case:
(i686-pc-windows-gnu has gcc, but it’s not on the $PATH).

    - name: Install prerequisites
      shell: bash
      run: |
        case ${{ matrix.job.target }} in
          arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
          aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
          i686-pc-windows-gnu) echo "C:\msys64\mingw32\bin" >> $GITHUB_PATH
        esac

Installing the Rust toolchain

    - name: Install Rust toolchain
      uses: actions-rs/toolchain@v1
      with:
        toolchain: stable
        target: ${{ matrix.job.target }}
        override: true
        profile: minimal # minimal component installation (ie, no documentation)

Building the executable

    - name: Build
      uses: actions-rs/cargo@v1
      with:
        use-cross: ${{ matrix.job.use-cross }}
        command: build
        args: --locked --release --target=${{ matrix.job.target }}

Stripping debug information from binary

    - name: Strip debug information from executable
      id: strip
      shell: bash
      run: |
        # Figure out suffix of binary
        EXE_suffix=""
        case ${{ matrix.job.target }} in
          *-pc-windows-*) EXE_suffix=".exe" ;;
        esac;
        # Figure out what strip tool to use if any
        STRIP="strip"
        case ${{ matrix.job.target }} in
          arm-unknown-linux-*) STRIP="arm-linux-gnueabihf-strip" ;;
          aarch64-pc-*) STRIP="" ;;
          aarch64-unknown-*) STRIP="" ;;
          armv7-unknown-*) STRIP="" ;;
          mips-unknown-*) STRIP="" ;;
          mips64-unknown-*) STRIP="" ;;
          mips64el-unknown-*) STRIP="" ;;
          mipsel-unknown-*) STRIP="" ;;
          powerpc-unknown-*) STRIP="" ;;
          powerpc64-unknown-*) STRIP="" ;;
          powerpc64le-unknown-*) STRIP="" ;;
          riscv64gc-unknown-*) STRIP="" ;;
          s390x-unknown-*) STRIP="" ;;
          x86_64-unknown-freebsd) STRIP="" ;;
          x86_64-unknown-illumos) STRIP="" ;;
        esac;
        # Setup paths
        BIN_DIR="${{ env.CICD_INTERMEDIATES_DIR }}/stripped-release-bin/"
        mkdir -p "${BIN_DIR}"
        BIN_NAME="${{ env.PROJECT_NAME }}${EXE_suffix}"
        BIN_PATH="${BIN_DIR}/${BIN_NAME}"
        TRIPLET_NAME="${{ matrix.job.target }}"
        # Copy the release build binary to the result location
        cp "target/$TRIPLET_NAME/release/${BIN_NAME}" "${BIN_DIR}"
        # Also strip if possible
        if [ -n "${STRIP}" ]; then
          "${STRIP}" "${BIN_PATH}"
        fi
        # Let subsequent steps know where to find the (stripped) bin
        echo ::set-output name=BIN_PATH::${BIN_PATH}
        echo ::set-output name=BIN_NAME::${BIN_NAME}

And uploading to Github

    - name: Create tarball
      id: package
      shell: bash
      run: |
        PKG_suffix=".tar.gz" ; case ${{ matrix.job.target }} in *-pc-windows-*) PKG_suffix=".zip" ;; esac;
        PKG_BASENAME=${PROJECT_NAME}-v${PROJECT_VERSION}-${{ matrix.job.target }}
        PKG_NAME=${PKG_BASENAME}${PKG_suffix}
        echo ::set-output name=PKG_NAME::${PKG_NAME}
        PKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/package"
        ARCHIVE_DIR="${PKG_STAGING}/${PKG_BASENAME}/"
        mkdir -p "${ARCHIVE_DIR}"
        mkdir -p "${ARCHIVE_DIR}/autocomplete"
        # Binary
        cp "${{ steps.strip.outputs.BIN_PATH }}" "$ARCHIVE_DIR"
        # base compressed package
        pushd "${PKG_STAGING}/" >/dev/null
        case ${{ matrix.job.target }} in
          *-pc-windows-*) 7z -y a "${PKG_NAME}" "${PKG_BASENAME}"/* | tail -2 ;;
          *) tar czf "${PKG_NAME}" "${PKG_BASENAME}"/* ;;
        esac;
        popd >/dev/null
        # Let subsequent steps know where to find the compressed package
        echo ::set-output name=PKG_PATH::"${PKG_STAGING}/${PKG_NAME}"
    - name: "Artifact upload: tarball"
      uses: actions/upload-artifact@master
      with:
        name: ${{ steps.package.outputs.PKG_NAME }}
        path: ${{ steps.package.outputs.PKG_PATH }}

    - name: Check for release
      id: is-release
      shell: bash
      run: |
        unset IS_RELEASE ; if [[ $GITHUB_REF =~ ^refs/tags/v[0-9].* ]]; then IS_RELEASE='true' ; fi
        echo ::set-output name=IS_RELEASE::${IS_RELEASE}
    - name: Publish archives and packages
      uses: softprops/action-gh-release@v1
      if: steps.is-release.outputs.IS_RELEASE
      with:
        files: |
          ${{ steps.package.outputs.PKG_PATH }}
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 

And after building this github actions file, we find that… 3 targets
fail to build.

x86_64-unknown-freebsd,
x86_64-unknown-illumos,
powerpc-unknown-linux-gnu.

Luckily, the error message that cross provides gives us a clear
indication of what to fix. Cross does not provide a proper image, so it
gets confused, defaults to the toolchain it’s running on (ubuntu 20.04),
and the linker cannot find the proper libraries required. Easy to fix:
Add a Cross.toml file to the root of the project with
docker images for the particular targets, and build again.

[target.x86_64-unknown-freebsd]
image = "svenstaro/cross-x86_64-unknown-freebsd"

[target.powerpc64-unknown-linux-gnu]
image = "japaric/powerpc64-unknown-linux-gnu"

You’ll notice that illumos is missing here – I couldn’t find a
suitable docker image to build it on docker hub, so I gave up. If you
find one, let me know and i’ll update this article.

Results

Out of the 29 architectures provided in Tier 1 and Tier 2 with host
tools, it was easy enough to build a binary for 28 architectures (We
only need a solaris/illumos docker image to build for the last one).

That’s pretty good, given that this only took a couple of hours to
test out. I hope Rust continues to support this many architectures into
the future, and Github Actions keeps being a good platform to make
releases for.

If you’d like to take the repo for yourself to build rust binaries on
releases for 28 architectures, feel free to clone/fork the repo here: https://github.com/Takashiidobe/rust-build-binary-github-actions

Время на прочтение10 мин

Количество просмотров19K

Наверное не будет уж очень удивительным если я тут, на IT площадке Хабра, скажу что я иногда балую себя программированием.

Основная OS у меня Linux, но иногда приходится собирать исполняемые файлы и для Windows. И естественно что перегружаться в Windows только для сборки exe не особо хочется. С языками C и C++ проблем нет, давно существует кросскомпилятор MinGW, который прекрасно с этим справляется. Про Python и Java даже упоминать не стоит, кроссплатформенность в них изначально. Но в прошлом году я решил попробовать такой пока что новомодный язык, как Rust. При сборке исполняемого файла при помощи включённого в дистрибутив Rust пакетного менеджера cargo вроде как достаточно задать ключ --target, при помощи которого указать результирующий процессор, архитектуру и ABI и при сборке из Linux в результате получить exe, который будет являться стандартным исполняемым файлом для Windows. Но пытаясь так сделать:

cargo build --target x86_64-pc-windows-gnu

я получил только сообщения об ошибках линкера:

error: linking with `gcc` failed: exit code: 1

[...]

  = note: /usr/bin/ld: unrecognized option '--nxcompat'
          /usr/bin/ld: use the --help option for usage information
          collect2: error: ld returned 1 exit status

error: aborting due to previous error

error: could not compile `foobar`.

Если кому интересно как я это поборол и теперь спокойно могу кросскомпилировать программы на Rust для Windows, не покидая Linux, добро пожаловать под кат.

Disclaimer

Далее я рассматриваю только цели 32bit и 64bit pc-windows-gnu, цели pc-windows-msvc для меня интереса не представляют и поэтому в них я не углублялся. Так же речь будет идти о том дистрибутиве Linux, который установлен на моём компьютере, то есть Fedora Linux 31, но я не думаю что на других дистрибутивах Linux будут очень уж существенные различия. И я использую Rust установленный при помощи The Rust toolchain installer, а не входящий в репозиторий Fedora Rust по причине того, что мне иногда требуются nightly сборки Rust, которых в стандартном репозитории, естественно, нет.

Первым делом убеждаемся что у нас установлены необходимые цели, запустив следующую команду:

rustup target list

Получаем список всех возможных целей, и целей, которые у нас установлены:

aarch64-apple-ios
aarch64-fuchsia
[...]
i686-pc-windows-gnu (installed)
[...]
i686-unknown-linux-gnu (installed)
[...]
x86_64-pc-windows-gnu (installed)
x86_64-unknown-linux-gnu (installed)
[...]

Для создания исполняемых файлов для Windows из Linux нам необходимы цели i686-pc-windows-gnu для 32bit exe и x86_64-pc-windows-gnu для 64bit exe. Если данные цели не отмечены как (installed), то доставляем их при помощи команды

rustup target add имя_цели

После убеждаемся что у нас установлен кросскомпилятор MinGW, запустив

rpm -qa | grep mingw

или другой пакетный менеджер для нашего дистрибутива Linux:

mingw32-gcc-9.2.1-1.fc31.x86_64
mingw32-binutils-2.32-6.fc31.x86_64
mingw64-gcc-9.2.1-1.fc31.x86_64
mingw-binutils-generic-2.32-6.fc31.x86_64
mingw-filesystem-base-110-1.fc31.noarch
mingw64-winpthreads-6.0.0-2.fc31.noarch
mingw32-winpthreads-6.0.0-2.fc31.noarch
mingw32-crt-6.0.0-2.fc31.noarch
mingw64-binutils-2.32-6.fc31.x86_64
mingw64-crt-6.0.0-2.fc31.noarch
mingw64-filesystem-110-1.fc31.noarch
mingw32-filesystem-110-1.fc31.noarch
mingw32-cpp-9.2.1-1.fc31.x86_64
mingw64-headers-6.0.0-2.fc31.noarch
mingw32-headers-6.0.0-2.fc31.noarch
mingw64-cpp-9.2.1-1.fc31.x86_64

При отсутствии MinGW устанавливаем необходимые пакеты, запустив

sudo dnf install mingw32-gcc mingw64-gcc

Ну вот вроде бы теперь всё в наличии, далее будем решать проблемы по мере их появления (ага, можно сказать что это получается прям какой-то Test-Driven Development, :-)

Создаём простейший проект на языке Rust:

[pfemidi@pfemidi rust]$ cargo new foobar
     Created binary (application) `foobar` package
[pfemidi@pfemidi rust]$ cat foobar/src/main.rs 
fn main() {
    println!("Hello, world!");
}
[pfemidi@pfemidi rust]$

Сначала компилируем и запускаем его как родное приложение Linux:

[pfemidi@pfemidi foobar]$ cargo run
   Compiling foobar v0.1.0 (/home/pfemidi/mywork/rust/foobar)
    Finished dev [unoptimized + debuginfo] target(s) in 1.65s
     Running `target/debug/foobar`
Hello, world!
[pfemidi@pfemidi foobar]$ 

Всё работает. Теперь пробуем его собрать как цель x86_64-pc-windows-gnu:

cargo build --target x86_64-pc-windows-gnu

и получаем всё то же сообщение об ошибке сборки:

error: linking with `gcc` failed: exit code: 1

[...]

  = note: /usr/bin/ld: unrecognized option '--nxcompat'
          /usr/bin/ld: use the --help option for usage information
          collect2: error: ld returned 1 exit status

error: aborting due to previous error

error: could not compile `foobar`.

Понятно, для сборки вызывается не линкер из MinGW, а уже установленный в системе gcc. Исправляем эту ситуацию, для этого создаём в проекте директорию .cargo и в ней файл config со следующим содержимым:

[pfemidi@pfemidi foobar]$ mkdir .cargo
[pfemidi@pfemidi foobar]$ cat > .cargo/config
[target.i686-pc-windows-gnu]
linker = "i686-w64-mingw32-gcc"
ar = "i686-w64-mingw32-ar"

[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"
ar = "x86_64-w64-mingw32-ar"
[pfemidi@pfemidi foobar]$

Это необходимо для того чтобы при сборке целей для Windows в качестве линкера использовался не установленный в системе gcc, а линкер из MinGW.

Пробуем собрать проект снова:

cargo build --target x86_64-pc-windows-gnu

и получаем другую ошибку от линкера, уже от x86_64-w64-mingw32-gcc:

error: linking with `x86_64-w64-mingw32-gcc` failed: exit code: 1

[...]

  = note: /usr/lib/gcc/x86_64-w64-mingw32/9.2.1/../../../../x86_64-w64-mingw32/bin/ld: cannot find -lpthread
          collect2: error: ld returned 1 exit status

error: aborting due to previous error

error: could not compile `foobar`.

Дело в том, что Rust по-умолчанию собирает всё в статическом виде, поэтому кроме пакетов mingw32-winpthreads и mingw64-winpthreads, которые dnf автоматически установил как зависимости для mingw32-gcc и mingw64-gcc обязательно должны быть установлены пакеты статических библиотек mingw32-winpthreads-static и mingw64-winpthreads-static, без них линкер всё время будет жаловаться на отсутствующий -lpthread и сборка не пройдёт. Доустанавливаем недостающие пакеты:

sudo dnf install mingw??-winpthreads-static

и опять запускаем компиляцию:

cargo build --target x86_64-pc-windows-gnu

Опять ошибка линковки! Но уже другая:

error: linking with `x86_64-w64-mingw32-gcc` failed: exit code: 1

[...]

  = note: /usr/lib/gcc/x86_64-w64-mingw32/9.2.1/../../../../x86_64-w64-mingw32/bin/ld: /home/pfemidi/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-pc-windows-gnu/lib/crt2.o:crtexe.c:(.rdata$.refptr.__onexitbegin[.refptr.__onexitbegin]+0x0): undefined reference to `__onexitbegin'
          /usr/lib/gcc/x86_64-w64-mingw32/9.2.1/../../../../x86_64-w64-mingw32/bin/ld: /home/pfemidi/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-pc-windows-gnu/lib/crt2.o:crtexe.c:(.rdata$.refptr.__onexitend[.refptr.__onexitend]+0x0): undefined reference to `__onexitend'
          collect2: error: ld returned 1 exit status

error: aborting due to previous error

error: could not compile `foobar`.

Линкер жалуется на отсутствующие символы __onexitbegin и __onexitend в файле ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-pc-windows-gnu/lib/crt2.o, который мы установили в составе цели x86_64-pc-windows-gnu. После некоторых раздумий, гугления, чтения доков на сайте Rust, изучения исходников самого Rust, того как и чем сам Rust собирается я понял: дело в том что сам Rust для Windows, и соответственно его компоненты для целей pc-windows-gnu, собраны с использованием MinGW 6.3.0, а у меня в Fedora Linux 31 версия MinGW 9.2.1, поэтому и происходит несоответствие в CRT. Ok, попробуем перенести crt2.o из федориного MinGW в директорию Rust для цели x86_64-pc-windows-gnu. И кроме crt2.o перенесём ещё и dllcrt2.o, который является точкой входа для динамических библиотек:

[pfemidi@pfemidi foobar]$ cd ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-pc-windows-gnu/lib/
[pfemidi@pfemidi lib]$ cp /usr/x86_64-w64-mingw32/sys-root/mingw/lib/crt2.o .
[pfemidi@pfemidi lib]$ cp /usr/x86_64-w64-mingw32/sys-root/mingw/lib/dllcrt2.o .
[pfemidi@pfemidi lib]$ cd -
/home/pfemidi/mywork/rust/foobar
[pfemidi@pfemidi foobar]$ 

и опять запускаем компиляцию нашего проекта на Rust:

pfemidi@pfemidi foobar]$ cargo build --target x86_64-pc-windows-gnu
   Compiling foobar v0.1.0 (/home/pfemidi/mywork/rust/foobar)
    Finished dev [unoptimized + debuginfo] target(s) in 4.46s
[pfemidi@pfemidi foobar]$

Прекрасно! Всё собралось! Т.к. у меня установлен wine, то тут же я могу и проверить как это работает:

[pfemidi@pfemidi foobar]$ cargo run --target x86_64-pc-windows-gnu
    Finished dev [unoptimized + debuginfo] target(s) in 0.38s
     Running `target/x86_64-pc-windows-gnu/debug/foobar.exe`
Hello, world!
[pfemidi@pfemidi foobar]$

И даже работает! Теперь пробуем сделать то же самое для 32bit версии исполняемого файла Windows, делаем сразу run без предварительного build:

error: linking with `i686-w64-mingw32-gcc` failed: exit code: 1

[...]

  = note: /usr/lib/gcc/i686-w64-mingw32/9.2.1/../../../../i686-w64-mingw32/bin/ld: /home/pfemidi/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-pc-windows-gnu/lib/crt2.o:crtexe.c:(.text+0x75): undefined reference to `__onexitend'
          /usr/lib/gcc/i686-w64-mingw32/9.2.1/../../../../i686-w64-mingw32/bin/ld: /home/pfemidi/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-pc-windows-gnu/lib/crt2.o:crtexe.c:(.text+0x7a): undefined reference to `__onexitbegin'
          collect2: error: ld returned 1 exit status

error: aborting due to previous error

error: could not compile `foobar`.

Ошибку с отсутствием символов __onexitbegin и __onexitend теперь уже в файле ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-pc-windows-gnu/lib/crt2.o мы уже проходили, лечится точно так же, как и для 64bit цели заменой файлов crt2.o и dllcrt2.o на аналогичные по именам, но из дистрибутива MinGW из Fedora:

[pfemidi@pfemidi foobar]$ cd ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-pc-windows-gnu/lib/
[pfemidi@pfemidi lib]$ cp /usr/i686-w64-mingw32/sys-root/mingw/lib/crt2.o .
[pfemidi@pfemidi lib]$ cp /usr/i686-w64-mingw32/sys-root/mingw/lib/dllcrt2.o .
[pfemidi@pfemidi lib]$ cd -
/home/pfemidi/mywork/rust/foobar
[pfemidi@pfemidi foobar]$ 

Проверяем:

[pfemidi@pfemidi foobar]$ 
[pfemidi@pfemidi foobar]$ cargo run --target i686-pc-windows-gnu
   Compiling foobar v0.1.0 (/home/pfemidi/mywork/rust/foobar)
    Finished dev [unoptimized + debuginfo] target(s) in 5.12s
     Running `target/i686-pc-windows-gnu/debug/foobar.exe`
Hello, world!
[pfemidi@pfemidi foobar]$

Тут теперь тоже всё собирается и работает.

И всё было прекрасно пока я не использовал никакие функции, которые паникуют (macro panic!, функция expect и т.д.) в 32bit целях для Windows. В целях 64bit всё хорошо, а вот в целях 32bit нет.

Добавим в наш проект панику:

[pfemidi@pfemidi foobar]$ cat src/main.rs 
fn main() {
    println!("Hello, world!");
    panic!("I'm panicked!");    // ВОТ НАША ПАНИКА!
}
[pfemidi@pfemidi foobar]

и попробуем собрать как исполняемый файл для 64bit Windows:

[pfemidi@pfemidi foobar]$ cargo run --target x86_64-pc-windows-gnu
   Compiling foobar v0.1.0 (/home/pfemidi/mywork/rust/foobar)
    Finished dev [unoptimized + debuginfo] target(s) in 2.95s
     Running `target/x86_64-pc-windows-gnu/debug/foobar.exe`
Hello, world!
thread 'main' panicked at 'I'm panicked!', src/main.rs:3:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
[pfemidi@pfemidi foobar]$

И компилируется, и собирается, и работает. Попробуем теперь сделать то же самое, но в качестве цели укажем 32bit Windows.

Упс:

[pfemidi@pfemidi foobar]$ cargo run --target i686-pc-windows-gnu
   Compiling foobar v0.1.0 (/home/pfemidi/mywork/rust/foobar)
error: linking with `i686-w64-mingw32-gcc` failed: exit code: 1

[...]

  = note: /usr/lib/gcc/i686-w64-mingw32/9.2.1/../../../../i686-w64-mingw32/bin/ld: /home/pfemidi/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-pc-windows-gnu/lib/libpanic_unwind-1a1fb2d4d34efaf8.rlib(panic_unwind-1a1fb2d4d34efaf8.panic_unwind.2hbcqjo8-cgu.0.rcgu.o): in function `ZN12panic_unwind3imp5panic17hdaabfe6326236dacE':
          /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\/src\libpanic_unwind/gcc.rs:73: undefined reference to `_Unwind_RaiseException'
          /usr/lib/gcc/i686-w64-mingw32/9.2.1/../../../../i686-w64-mingw32/bin/ld: /home/pfemidi/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-pc-windows-gnu/lib/libpanic_unwind-1a1fb2d4d34efaf8.rlib(panic_unwind-1a1fb2d4d34efaf8.panic_unwind.2hbcqjo8-cgu.0.rcgu.o): in function `rust_eh_unwind_resume':
          /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8\/src\libpanic_unwind/gcc.rs:327: undefined reference to `_Unwind_Resume'
          collect2: error: ld returned 1 exit status

error: aborting due to previous error

error: could not compile `foobar`.

Опять линкер жалуется на отсутствие символов, но теперь это символы _Unwind_RaiseException и _Unwind_Resume в модуле libpanic стандартной библиотеки Rust.

Снова раздумия, снова гугление, снова чтение доков и изучение исходников как самого Rust, так и его стандартной библиотеки. И я понял почему возникает такая ошибка.

Для разматывания стека при исключении Rust использует метод Dwarf для 32bit целей Windows и SEH для 64bit целей Windows, а MinGW из стандартного репозитория Fedora Linux использует метод SJLJ для 32bit целей Windows и SEH для 64bit целей Windows (о различии между этими методами читать тут). Поэтому 64bit цели собираются без вопросов, а для 32bit просто нет необходимых символов и объектных файлов. Чтобы получить данные файлы необходимо пересобрать MinGW с поддержкой Dwarf вместо поддерки SJLJ по умолчанию для 32bit целей Windows.

Я не буду вдаваться в подробности как именно пересобирать MinGW, это уже не так сложно и не так интересно (configure там надо запускать с параметром --disable-sjlj-exceptions, остальное тривиально), скажу только одно: после того как MinGW пересобран с разматыванием стека Dwarf вместо SJLJ оттуда надо взять всего один файл под названием libgcc_eh.a и положить его в директорию с библиотеками для цели i686-pc-windows-gnu. После этого проекты в которых используются паникующие функции начнут собираться не только для 64bit целей Windows, но и для 32bit:

[pfemidi@pfemidi foobar]$ cd ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-pc-windows-gnu/lib/
[pfemidi@pfemidi lib]$ cp ~/rpmbuild/BUILD/gcc-9.2.1-20190827/build_win32/i686-w64-mingw32/libgcc/libgcc_eh.a .
[pfemidi@pfemidi lib]$ cd -
/home/pfemidi/mywork/rust/foobar
[pfemidi@pfemidi foobar]$ cargo run --target i686-pc-windows-gnu
   Compiling foobar v0.1.0 (/home/pfemidi/mywork/rust/foobar)
    Finished dev [unoptimized + debuginfo] target(s) in 4.57s
     Running `target/i686-pc-windows-gnu/debug/foobar.exe`
Hello, world!
thread 'main' panicked at 'I'm panicked!', src/main.rs:3:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
[pfemidi@pfemidi foobar]$ 

Ну вот, как-то так.

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Epson perfection 3590 photo driver windows 10
  • Kinect adapter for windows
  • 8gadgetpacksetup для windows 11
  • Как отключить приглушение звука в windows 11
  • Как узнать какой версии у тебя windows