-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
RFC: cfg_os_version_min #3750
base: master
Are you sure you want to change the base?
RFC: cfg_os_version_min #3750
Changes from 3 commits
c08880c
d78f82b
58ae3de
0b8f912
c3f33b8
6e2245c
35dc984
5332c9b
859a733
6c7e0c6
16c6321
68c00f8
6b54296
a9b5b74
b71051b
5cde247
35896d1
439fcea
89b604d
c889a32
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
- Feature Name: `cfg_os_version_min` | ||
- Start Date: 2024-12-27 | ||
- RFC PR: [rust-lang/rfcs#3750](https://github.com/rust-lang/rfcs/pull/3750) | ||
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
A new `cfg` predicate `os_version_min` that allows users to declare the minimum primary (target-defined) API level required/supported by a block. | ||
E.g. `cfg!(os_version_min("windows", "6.1.7600"))` would match Windows version >= 6.1.7600. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
The target API version is the version number of the "API set" that a particular binary relies on in order to run properly. An API set is the set of APIs that a host operating system makes available for use by binaries running on that platform. Newer versions of a platform may either add or remove APIs from the API set. | ||
|
||
Crates including the standard library must account for various API version requirements for the crate to be able to run. Rust currently has no mechanism for crates to compile different code (or to gracefully fail to compile) depending on the minimum targeted API version. This leads to the following issues: | ||
|
||
* Relying on dynamic detection of API support has a runtime cost. The standard library often performs [dynamic API detection](https://github.com/rust-lang/rust/blob/f283d3f02cf3ed261a519afe05cde9e23d1d9278/library/std/src/sys/windows/compat.rs) falling back to older (and less ideal) APIs or forgoing entire features when a certain API is not available. For example, the [current `Mutex` impl](https://github.com/rust-lang/rust/blob/234099d1d12bef9d6e81a296222fbc272dc51d89/library/std/src/sys/windows/mutex.rs#L1-L20) has a Windows XP fallback. Users who only ever intend to run their code on newer versions of Windows will still pay a runtime cost for this dynamic API detection. Providing a mechanism for specifying which minimum API version the user cares about, allows for statically specifying which APIs a binary can use. | ||
ChrisDenton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* Certain features cannot be dynamically detected and thus limit possible implementations. The libc crate must use [a raw syscalls on Android for `accept4`](https://github.com/rust-lang/libc/pull/1968), because this was only exposed in libc in version 21 of the Android API. Additionally libstd must dynamically load `signal` for all versions of Android despite it being required only for versions 19 and below. In the future there might be similar changes where there is no way to implement a solution for older versions. | ||
* Trying to compile code with an implicit dependency on a API version greater than what is supported by the target platform leads to linker errors. For example, the `x86_64-pc-windows-msvc` target's rustc implementation requires `SetThreadErrorMode` which was introduced in Windows 7. This means trying to build the compiler on older versions of Windows will fail with [a less than helpful linker error](https://github.com/rust-lang/rust/issues/35471). | ||
|
||
ChrisDenton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# Guide-level explanation | ||
[guide-level-explanation]: #guide-level-explanation | ||
|
||
Rust targets are often thought of as monoliths. | ||
The thought is that if you compile a binary for that target, that binary should be able to run on any system that fits that target's description. | ||
However, this is not actually true. | ||
For example, when compiling for `x86_64-pc-windows-msvc` and linking with the standard library, my binary has implicitly taken a dependency on a set of APIs that Windows exposes for certain functionality. | ||
If I try to run my binary on older systems that do not have those APIs, then my binary will fail to run. | ||
When compiling for a certain target, you are therefore declaring a dependency on a minimum target API version that you rely on for your binary to run. | ||
|
||
Each standard library target uses a sensible minimum API version. for `x86_64-pc-windows-msvc` the minimum API version is "10.0.10240" which corresponds to Windows 10's initial release. | ||
For `x86_64-win7-pc-windows-msvc` the minimum API version is "6.1.7600" which corresponds to Windows 7. | ||
ChrisDenton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
However, inferring the API version from the target name isn't ideal especially as it can change over time. | ||
|
||
Instead you use the `os_version_min` predicates to specify the minimum API levels of various parts of the operating system. For example: | ||
|
||
* `os_version_min(“windows”, <string>)` would test the [minimum build version](https://gaijin.at/en/infos/windows-version-numbers) of Windows. | ||
* `os_version_min(“libc”, <string>)` would test the version of libc. | ||
* `os_version_min(“kernel”, <string>)` would test the version of the kernel. | ||
ChrisDenton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Let’s use `os_version_min(“windows”, …)` as an example. It should be clear how this example would be extended to the other `cfg` predicates. The predicate allows you to conditionally compile code based on the set minimum API version. For example an implementation of mutex locking on Windows might look like this: | ||
|
||
```rust | ||
pub unsafe fn unlock(&self) { | ||
*self.held.get() = false; | ||
if cfg!(os_version_min(“windows”, "6.0.6000") { // API version greater than Vista | ||
ChrisDenton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
c::ReleaseSRWLockExclusive(raw(self)) // Use the optimized ReleaseSRWLockExclusive routine | ||
} else { | ||
(*self.remutex()).unlock() // Fall back to an alternative that works on older Windows versions | ||
} | ||
} | ||
``` | ||
|
||
For targets where `os_version_min(“windows”, …)` does not make sense (i.e., non-Windows targets), the `cfg` predicate will return `false` and emit a warning saying that the particular `cfg` predicate is not supported on that target. Therefore, it's important to pair `os_version_min(“windows”, …)` with a `cfg(windows)` using the existing mechanisms for combining `cfg` predicates. | ||
ChrisDenton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
The above example works exactly the same way with the other platform API `cfg` predicates just with different values and different target support. | ||
|
||
These predicates do not assume any semantic versioning information. The specified predicates are simply listed in order. The only semantics that are assumed is that code compiled with the `cfg` predicates works for all versions greater than or equal to that version. | ||
|
||
**Note:** Here it would be important to link to documentation showing the `cfg` predicates and the different version strings that are supported. | ||
|
||
# Reference-level explanation | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we need to discuss here what happens when you link libraries compiled with different (even though we're not specifying how the user, we will need a mechanism for it at some point) Specifically, it'd be nice to talk about the pre-compiled standard library, and how it effectively becomes a requirement to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Possibly also interesting is soundness concerns (what happens if I use a standard library compiled with a newer version of glibc/Windows APIs/macOS APIs, while I link with a binary compiled for older APIs?). I don't think there are any soundness concerns, at least not on Apple platforms (the dynamic linker will simply fail to work if using an API for a newer system), but it's important that we're certain of this (and that function pointers for example aren't simply replaced by NULL if loaded on an older OS). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There aren't unsoundness issues for Windows. Either a dll or symbol is available or it isn't and there's an error. EDIT: I'm being told that attempting to use incompatible glibcs will also cause an error. I don't know if that's true of all OSes but Rust libs do carry around metadata with them so if they declare incompatible versions then the compiler could simply error. And linking together native static libraries compiled for different API versions would seem to be squarely in the realm of "you must know what you're doing" (and that's true more broadly when linking native libs). However, considering the current narrow scope of this RFC, it would not be a situation that arises any more often than it currently does. So it may only worth a mention in future possibilities. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd actually like to update my previous statement, I suspect that it may actually be unsound to use the combination Related here is #3716. But I agree that this is only tangentially related to the RFC. |
||
[reference-level-explanation]: #reference-level-explanation | ||
|
||
The `os_version_min` predicate allows users to conditionally compile code based on the API version supported by the target platform. | ||
Each platform is responsible for defining a default key, a set of keys it supports, and functions that are able to compare the version strings they use. | ||
A set of comparison functions can be provided by `rustc` for common formats such as 2- and 3-part semantic versioning. | ||
When a platform detects a key it doesn’t support it will return `false` and emit a warning. | ||
ChrisDenton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Each target platform will set the minimum API versions it supports. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So there is no way for the user to configure which minimum API version they want to use? That would make it impossible for eg the libc crate to gate api's behind There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. One of the issues with the original proposals was there was too much going on. This RFC aims to add the minimally useful feature. Adding compiler flags and Cargo configs to control the minimum version can be a future extension. With this RFC it is still possible for libc to gate APIs like that. However, you'd need a new target in order to set a different minimal libc version. |
||
|
||
## Versioning Schema | ||
|
||
Version strings can take on nearly any form and while there are some standard formats, such as semantic versioning or release dates, projects can change schemas or provide aliases for some or all of their releases. | ||
Because of this diversity in version strings each platform will be responsible for defining a type implementing `FromStr`, `Display`, and `Ord` for each key they support (or using one of the pre-defined types). | ||
|
||
## Future Compatibility | ||
|
||
The functions for parsing and comparing version strings will need to be updated whenever a new API is added, when the version format changes, or when new aliases need to be added. | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
Each supported platform will need to implement version string parsing logic (or re-use some provided defaults), maintain the logic in response to future changes, and update any version alias tables. | ||
|
||
# Rationale and alternatives | ||
[rationale-and-alternatives]: #rationale-and-alternatives | ||
|
||
The overall mechanism proposed here builds on other well established primitives in Rust such as `cfg`. | ||
|
||
A mechanism which tries to bridge cross-platform differences under one `min_target_api_version` predicate [was suggested](https://github.com/rust-lang/rfcs/blob/b0f94000a3ddbd159013e100e48cd887ba2a0b54/text/0000-min-target-api-version.md) but was rejected due to different platforms having divergent needs. | ||
|
||
# Prior art | ||
[prior-art]: #prior-art | ||
|
||
The Swift package manager has a way to [specify the supported platforms for a given package](https://docs.swift.org/package-manager/PackageDescription/PackageDescription.html#supportedplatform). | ||
ChrisDenton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
This RFC is largely a version of [RFC #3379](https://github.com/rust-lang/rfcs/pull/3036) more narrowly scoped to just the most minimal lang changes. | ||
ChrisDenton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
That RFC was in turn an updated version of [this RFC draft](https://github.com/rust-lang/rfcs/pull/3036), with the changes reflecting conversations from the draft review process and [further Zulip discussion](https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/CFG.20OS.20Redux.20.28migrated.29/near/294738760). | ||
|
||
# Unresolved questions | ||
ChrisDenton marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another unresolved question to add: How should this work in Cargo |
||
[unresolved-questions]: #unresolved-questions | ||
|
||
Custom targets usually specify their configurations in JSON files. | ||
It is unclear how the target maintainers would add functions, types, and version compatibility information to these files. | ||
|
||
# Future possibilities | ||
[future-possibilities]: #future-possibilities | ||
|
||
The compiler could allow setting a higher minimum OS version than the target's default. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remark: I'll add that |
||
With the `build-std` feature, each target could optionally support lowering the API version below the default. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.