Almost all sys crates have to deal with the question: should they link dynamically or statically?
It's such a simple binary question, but there is no good default for it. The reality is messy, dependent on the target platform, and the intended distribution channel for the application:
-
Linux distros prefer unbundling, so most sys crates on Linux should link dynamically. Except crates that are not available in a given distro, or when the application absolutely requires a version that is newer than what the distro ships with. And except MUSL and binaries distributed as tarballs rather than distro packages.
-
On macOS most libraries should be statically linked, except libraries that Apple ships with the OS. Except crates that are built as part of Homebrew's distribution should link everything dynamically, but not when merely using Homebrew to build for a different distribution channel.
-
On Windows most libraries should be statically linked, but applications may decide on a case-by-case basis to ship some libraries as DLLs installed by the application's installer.
I think this problem needs a dedicated solution, because:
-
Almost every sys crate needs to deal with this decision.
-
Even though there's no perfect default setting, picking even half-decent default behaviors requires awareness of multiple operating systems. Currently majority of Rust users are on Linux, but in this area Linux is significantly less messy than other platforms, so I'm afraid Linux users will underestimate complexity of the problem.
-
When sys crate's default behavior is unsuitable, and the user needs to customize the setting, Cargo gets in the way.
Cargo features are ill-suited for this problem. The obvious way of having two separate features for static
and dynamic
makes the features mutually-exclusive, and Cargo features are not supposed to be used like this. Having one as the default and the other as a feature flag is more Cargo-friendly, but less obvious, and it can't be an actual default
feature flag, because default-features=false
is practically impossible to use. In either case, if any crate in the dependency tree sets the wrong flag, overriding it is impossible. Setting of Cargo features for a sub-sub-dependencies is non-obvious and feels hacky.
Sys crates also use environment flags for this, but Cargo is also unhelpful for this. Cargo workspaces can't set global env flags, so projects need another solution (e.g. layer another build system) to configure the environment first. Env flags for sys crates are not discoverable. There's no agreed standard naming or a way to set the defaults.
There are also smaller configuration problems that may be relevant:
- If linking statically, should the crate use its vendored version, or search the system for a static version, or search some specific directory for a user-supplied custom build?
- Which version or ABI configuration of the library is required?
- Should the sys crate run bindgen, or can it use pregenerated bindings?
- If linking dynamically, what
rpath
should it set? (e.g. on Linux it's usually just system-global default, but macOS almost always needs special settings for bundles and frameworks) - LGPL sys crates may want to build a shared library from vendored sources, and let the app link to them. That's currently very tricky to do with cargo, because
OUT_DIR
is well-hidden, andrpath
is target-dependent.
On top of that, a project that is built for multiple platforms needs to have multiple versions of this configuration. Target triples are not even specific enough, because Windows apps may want to offer two configurations: with or without an installer ("portable" in Windows sense), and macOS builds are very different depending on whether they're Homebrew-dependent formulas or app bundles.
So in the end it's a negotiation between target system conventions and user requirements, and quirky needs of individual libraries. How should this config look like? Where should it go? How can sys crates be simpler to implement without making them ignore complexity of macOS and Windows?