Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracking issue for #[link(kind)] connecting libraries on Windows #37403

Closed
3 tasks done
alexcrichton opened this issue Oct 25, 2016 · 34 comments · Fixed by #95818
Closed
3 tasks done

Tracking issue for #[link(kind)] connecting libraries on Windows #37403

alexcrichton opened this issue Oct 25, 2016 · 34 comments · Fixed by #95818
Assignees
Labels
A-linkage Area: linking into static, shared libraries and binaries B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. B-unstable Blocker: Implemented in the nightly compiler and unstable. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC O-windows Operating system: Windows S-tracking-needs-summary Status: It's hard to tell what's been done and what hasn't! Someone should do some investigation. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@alexcrichton
Copy link
Member

alexcrichton commented Oct 25, 2016

Tracking issue for rust-lang/rfcs#1717

  • Implement -l[kind=][oldname:]libname.
  • Implement lib<->symbol association and emit dllimport for dylib symbols.
  • Implement kind="static-nobundle".
@dgrunwald
Copy link
Contributor

If I specify #[link(name="pythonXY")] on 30 extern C blocks, and then compile with rustc -l dylib=pythonXY:python27, then python27.lib is passed to the linker 30 times.

Not sure if that's actually a problem, but maybe Rust could de-duplicate the linker arguments?

@dgrunwald
Copy link
Contributor

Also, is it intentional that these changes are insta-stable?

cc @vadimcn

@vadimcn
Copy link
Contributor

vadimcn commented Dec 17, 2016

@dgrunwald: Could you coalesce those 30 extern blocks into one? You probably did it to keep extern declarations in the same file/module as the code using them, but this isn't really how we thought about extern blocks when designing Rust FFI.

The problem with de-duping is that most unix linkers resolve references using only libraries to the right on the command line. So if you know that lib1 depends on something in lib2, they must appear in that order (even if lib2 has already appeared to the left of lib1).

Our approach so far has been to simply emit libraries in the order they appear in the source, followed by the ones specified on the command line.

In order to optimize this, we'd need to provide some way of specifying library dependencies (e.g. "libraries {attached to the same extern block | within the same module | within the same source file} are guaranteed to be given to the linker in the same order"). For starters, some motivated person would need to write an RFC :)

Also, is it intentional that these changes are insta-stable?

Sort of. The changes in handling of dllimport did not create any new syntax that could be feature gated. We could still use a #[feature(...)] to toggle the new behavior, but it's a bit too subtle for my liking: There would be no error message telling the user that this is feature-gated; they'd simply get a linker error.

@dgrunwald
Copy link
Contributor

python3-sys is structured in the same way as the python headers: one .rs file per .h file; with the declarations within the file in the same order as in the .h file.
Because the Python headers have lots of macros (which I manually translate into constants or #[inline] fns), I frequently close the extern "C" block, define the macro functions/constants, and then re-open the extern "C" block. This approach means that python3-sys has 95 #[link(name="pythonXY")] extern "C" blocks in total.

When a new Python version is released, I diff the new Python headers against the old headers and apply the same changes to the Rust code (using #[cfg] attributes for new functions). This would be much more difficult if I had to keep everything in a single extern block.

@retep998
Copy link
Member

retep998 commented Dec 18, 2016

@dgrunwald A similar situation will occur for me in winapi = "0.3". Preserving duplicates isn't strictly necessary everywhere. link.exe and lld in particular aren't dumb the way unix linkers are, so we can trivially deduplicate libraries for the msvc linker without breaking anything, and eventually when we switch to LLD for all the other targets we can deduplicate libraries everywhere. Until then though, we need some sort of solution.

@dgrunwald
Copy link
Contributor

The current logic seems to be: if both #[link(X)] and -l X:Y is specified, ignore the position of the -l X:Y command-line argument and use all the #[link] positions instead.

What if we just did this the other way around: if both are specified, ignore the #[link] positions and use the single -l X:Y position instead.

@vadimcn
Copy link
Contributor

vadimcn commented Dec 18, 2016

In any case, this is not a new issue and has nothing to do with dllimport annotations. Let's continue this discussion in #38460.

bors added a commit that referenced this issue Feb 4, 2017
Implement kind="static-nobundle" (RFC 1717)

This implements the "static-nobundle" library kind (last item from #37403).

Rustc handles "static-nobundle" libs very similarly to dylibs, except that on Windows, uses of their symbols do not get marked with "dllimport".  Which is the whole point of this feature.
@brson brson added the B-unstable Blocker: Implemented in the nightly compiler and unstable. label Mar 1, 2017
@Mark-Simulacrum Mark-Simulacrum added T-dev-tools Relevant to the dev-tools subteam, which will review and decide on the PR/issue. and removed T-tools labels May 24, 2017
@Mark-Simulacrum Mark-Simulacrum added the C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC label Jul 22, 2017
@retep998
Copy link
Member

Would it be possible to get some focus on bikeshedding the name of kind=static-nobundle and perhaps stabilizing it?

indygreg added a commit to indygreg/rust-cpython that referenced this issue Jan 20, 2020
As part of developing PyOxidizer, I needed to force python3-sys to
statically link against a Python library on Windows in a downstream
crate of python3-sys. This requires the unstable `static-nobundle`
link type so Cargo leaves symbols as unresolved when python3-sys
is built. (Currently, the `static` linkage type verifies referenced
symbols are present at crate build time.) See
rust-lang/rust#37403 for more. Look for
comments by me (@indygreg) to describe the issue in more detail.

This commit teaches python3-sys a pair of new build features which
enable more explicit control over the linker directives emitted by
its build script. If no directive is specified, `link-mode-default`
is used and the existing logic for linker directive emission is
used. If `link-mode-unresolved-static` is used and we're on Windows,
we emit a `static-nobundle=pythonXY` linker directive and
omit the location of the library. This effectively says "I depend
on a static `pythonXY` library but don't resolve the symbols
when you build me and require someone else to specify the location
to that library." What PyOxidizer does is emit its own linker
directive that defines the location of a static `pythonXY` library,
satisfying the linker constraint and enabling the build to work.
If a downstream crate doesn't do this, the build should fail due
to a missing library or symbols.

I have purposefully designed the crate features to be extensible.
If we want to add additional, mutually exclusive features in the
future, we could do that. e.g. we could add a `link-mode-static`
that force emits a `rustc-link-lib=static=pythonXY` directive
to force static linking, even if a shared library is detected.
But I have no need for this today and don't want to complicate
the code, so I haven't added it.

To round out the new feature, features have been added to the
cpython crate to toggle the new features.

Because Python 2.7 is end of life, I have not implemented the new
feature for Python 2.7. I suspect very few people will use this
feature anyway and I'm pretty confident that nobody will request
this feature on Python 2.7.

I concede that adding this feature to the crate to support
PyOxidizer's esoteric use case is a bit unfortunate. I really wish
Cargo allowed a crate to wholesale replace the build script output
of a dependency, as PyOxidizer could statically resolve the
Python settings for python3-sys since it brings its own Python
library. But Cargo doesn't have this feature. So I'm stuck
having to land this feature in the upstream crate to avoid having
to maintain a permanent fork of `rust-cpython`. Sorry :/
@jonas-schievink jonas-schievink added O-windows Operating system: Windows A-linkage Area: linking into static, shared libraries and binaries labels Jan 31, 2020
@petrochenkov
Copy link
Contributor

I have submitted an RFC about re-branding "nobundle" as a modifier for the static linking kind - rust-lang/rfcs#2951.

@petrochenkov
Copy link
Contributor

I also have a question - does anyone actually use the library renaming (the :rename bit in -lkind=name:rename) in practice?

bors added a commit to rust-lang-ci/rust that referenced this issue Aug 9, 2021
…ndle, r=petrochenkov

Fix feature gate checking of static-nobundle and native_link_modifiers

Feature native_link_modifiers_bundle don't need feature static-nobundle
to work.

Also check the feature gates when using native_link_modifiers from command line options. Current nighly compiler don't check those feature gate.

```
> touch lib.rs
> rustc +nightly lib.rs -L /usr/lib -l static:+bundle=dl --crate-type=rlib
> rustc +nightly lib.rs -L /usr/lib -l dylib:+as-needed=dl --crate-type=dylib -Ctarget-feature=-crt-static
> rustc +nightly lib.rs -L /usr/lib -l static:-bundle=dl --crate-type=rlib
error[E0658]: kind="static-nobundle" is unstable
  |
  = note: see issue rust-lang#37403 <rust-lang#37403> for more information
  = help: add `#![feature(static_nobundle)]` to the crate attributes to enable

error: aborting due to previous error

For more information about this error, try `rustc --explain E0658`.

```

First found this in rust-lang#85600 (comment)
@indygreg
Copy link
Contributor

I also have a question - does anyone actually use the library renaming (the :rename bit in -lkind=name:rename) in practice?

I think I do!

The pyembed crate depends on a libpython, whose bindings are provided by a separate crate (currently python3-sys soon to be pyo3). The crate providing the bindings is the one emitting the cargo:rustc-link-* lines to link libpython.

Here's the wrinkle and my use case. The name of the actual Python library being linked is dynamic. And, the pyembed crate's build script needs to access metadata about the Python interpreter/library that python3-sys / pyo3's build script resolves.

The pyembed crate's Cargo.toml defines a links = "..." to enable its build script to access DEP_* variables exported by python3-sys / pyo3's build script so metadata can be exchanged between the build scripts (https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key).

However, because the actual library name is dynamic and sniffed out by the python3-sys / pyo3 build scripts, the pyembed crate can't use a static value for the links = "..." config to represent the real library! So, the workaround is for python3-sys / pyo3 to define their extern "C" bindings as linking against pythonXY and have their build scripts emit the cargo:rustc-link-lib=pythonXY:real_library line to alias pythonXY to the dynamically discovered library name. pyembed uses links = "pythonXY", enabling the DEP_* variables to be defined and for metadata to be exchanged.

There is a wonky edge case where when using static-nobundle something somewhere insists on a pythonXY static library actually existing. I'm unsure if this is wonkiness in Cargo or how these particular crates are configured. But I work around it by writing out a placeholder pythonXY static library somewhere on the link path and the error goes away: the linker only cares that it can find the symbols, not necessarily that the symbols are defined in the exact library name annotated in an extern "C" block.

I recognize this use case is kinda wonky. If there were a better way to pass metadata from one build script to a dependent build script, I'd do that. But this links = "..." mechanism is what I've settled on.

@indygreg
Copy link
Contributor

... and a few hours later and I've refactored the pyembed crate to no longer depend on static-nobundle. I did this by teaching pyo3's build script to read a config file (specified via an environment variable that must be defined outside of cargo) which defines (among other things) a list of extra lines to println!() from the build script. The pyo3 build script now emits a complete definition of the static library it needs to link against, removing the need for the deferred symbol resolution via static-nobundle. Hacky. But it gets the job done.

I was fortunate to be working with a crate maintainer receptive to hacking up their build script to support my use case. Not everyone will be so fortunate, so this feature is still generally useful.

@petrochenkov petrochenkov self-assigned this Feb 16, 2022
@pnkfelix
Copy link
Member

pnkfelix commented Mar 4, 2022

@rustbot label +T-compiler -T-dev-tools

@rustbot rustbot added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. and removed T-dev-tools Relevant to the dev-tools subteam, which will review and decide on the PR/issue. labels Mar 4, 2022
@pnkfelix
Copy link
Member

pnkfelix commented Mar 4, 2022

Discussed in today's T-compiler backlog bonanza.

From quickly skimming the comments here, I can tell there are people invested in this feature, but I cannot tell what its current status is (how much has been stabilized, how much was insta-stabilized, how much is still unstable), nor how much remains to be done, if anything.

@rustbot label S-tracking-needs-summary

@rustbot rustbot added the S-tracking-needs-summary Status: It's hard to tell what's been done and what hasn't! Someone should do some investigation. label Mar 4, 2022
@petrochenkov
Copy link
Contributor

This issue is basically tracking the removal of static-nobundle now.

It's currently blocked on #93901 and on one more following PR stabilizing the bundle modifier.

@CoorFun
Copy link

CoorFun commented May 26, 2022

Hi there:

I am pointed here by this post, what I am actually trying to do now is that I want to find a way to link my rust final c dynamic library (.so) against stdlibc++ statically. So that this shared library can be used under another linux distribution which doesn't have the minimum requried stdlibc++ installed..

From the post, I fount that "static-nobundle" feature could help, but when I tried it in my lib, it shows me:

warning: library kind `static-nobundle` has been superseded by specifying modifier `-bundle` with library kind `static`. Try `static:-bundle`

So I changed println!("cargo:rustc-link-lib=static-nobundle=stdc++")to println!("cargo:rustc-link-lib=static:-bundle=stdc++") in the build.rs file

But still, I am not getting things compiled as expected:

error: linking modifier `bundle` is unstable and only accepted on the nightly compiler

I have enabled those two features in my lib.rs, and the toolchain used is nightly-2022-05-24-x86_64-unknown-linux-gnu

lib.rs:

#![feature(native_link_modifiers_bundle)]
#![feature(static_nobundle)]

Anyone has ideas? Thanks in advance!

Dylan-DPC added a commit to Dylan-DPC/rust that referenced this issue May 31, 2022
Stabilize the `bundle` native library modifier

And remove the legacy `static-nobundle` linking kind.

Stabilization report - rust-lang#95818 (comment).

cc rust-lang#81490
Closes rust-lang#37403
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this issue Jun 10, 2022
Stabilize the `bundle` native library modifier

And remove the legacy `static-nobundle` linking kind.

Stabilization report - rust-lang#95818 (comment).

cc rust-lang#81490
Closes rust-lang#37403
@bors bors closed this as completed in 3dea003 Jun 10, 2022
@AlexTMjugador
Copy link

Anyone has ideas? Thanks in advance!

Using static:-bundle=stdc++ worked for me on rustc 1.79.0-nightly (0bf471f33 2024-04-13) without enabling any feature flags. The rollup merge that closed this issue merged this PR, which stabilized the new linker modifier syntax: #95818

@gucki
Copy link

gucki commented May 2, 2024

@AlexTMjugador I can't get my cross-compiled executable to work. Running it always fails with "The code execution cannot proceed because libstdc++-6.dll was not found...".

This is my build.rs:

fn main() {
  println!("cargo:rustc-link-lib=static:-bundle=stdc++");
}

This is my docker image I use for the build:

FROM rustlang/rust:nightly

RUN apt update
RUN apt install -y mingw-w64 cmake
RUN update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix
RUN update-alternatives --set x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix
RUN update-alternatives --set i686-w64-mingw32-g++ /usr/bin/i686-w64-mingw32-g++-posix
RUN update-alternatives --set i686-w64-mingw32-gcc /usr/bin/i686-w64-mingw32-gcc-posix

RUN rustup target add x86_64-pc-windows-gnu

WORKDIR /build

Output of "cargo version --verbose" inside the container:

cargo 1.80.0-nightly (6087566b3 2024-04-30)
release: 1.80.0-nightly
commit-hash: 6087566b3fa73bfda29702632493e938b12d19e5
commit-date: 2024-04-30
host: x86_64-unknown-linux-gnu
libgit2: 1.7.2 (sys:0.18.3 vendored)
libcurl: 8.6.0-DEV (sys:0.4.72+curl-8.6.0 vendored ssl:OpenSSL/1.1.1w)
ssl: OpenSSL 1.1.1w  11 Sep 2023
os: Debian 12.0.0 [64-bit]

Do you know why the library is not statically linked?

@AlexTMjugador
Copy link

Do you know why the library is not statically linked?

My best guess as to why that occurred is that another thing is trying to dynamically link to the C++ stdlib. Most likely, that thing is another build script, or a configured linker flag. You can try building your project with Cargo's verbose flags to pinpoint the source of the troublesome linker parameters.

I was able to verify that the cargo:rustc-link-lib=static:-bundle=stdc++ instruction works as expected to link to the C++ stdlib. I spun up a fresh Docker container with your Dockerfile and newly created Cargo project with cargo new linking-test, appending the build.rs you provided to it. The output of x86_64-w64-mingw32-objdump confirms that the executable should not require libstdc++-6.dll at runtime:

root@b8083609ca3d:~/linking-test# x86_64-w64-mingw32-objdump -p target/x86_64-pc-windows-gnu/debug/linking-test.exe | grep 'DLL Name'
        DLL Name: KERNEL32.dll
        DLL Name: msvcrt.dll
        DLL Name: ntdll.dll
        DLL Name: USERENV.dll
        DLL Name: WS2_32.dll
        DLL Name: api-ms-win-core-synch-l1-2-0.dll
        DLL Name: bcryptprimitives.dll

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-linkage Area: linking into static, shared libraries and binaries B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. B-unstable Blocker: Implemented in the nightly compiler and unstable. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC O-windows Operating system: Windows S-tracking-needs-summary Status: It's hard to tell what's been done and what hasn't! Someone should do some investigation. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.