One thing I'm missing in the Rust ecosystem of bindings to C libraries is easy conditional compilation based on the build-time library version. Something like the GTK_CHECK_VERSION() macros in C.

In Rust bindings, it's common to expose some baseline version API, then have feature flags to expose (and depend on) newer versions of the library. But there's no simple way to do, as a downstream user:

#[cfg(have_gtk_4_12)]
use_new_api();
#[cfg(not(have_gtk_4_12))]
fallback_with_old_api();

#rust

Related: how do distros track these build-time dependency checks? E.g. my app did a build-time check and used the GTK < 4.12 code path, then the distro updated GTK to 4.12, so now my app needs to be rebuilt to take advantage of the new API. How is this tracked?

I guess this question is more relevant for rolling-release distros, but it's very possible for a build-time check to test for a minor version which would be updated even in a non-rolling-release distro.

#linux #fedora

@YaLTeR well, I suppose they put gtk as a dependency of the program, and then they rebuild all its dependents, although probably it's not quite as simple as I'm thinking here. I can totally imagine, for example, arch, doing that

@esoteric_programmer I'd expect the opposite, i.e. dependencies aren't tracked this precisely, and updates to shared libraries never cause a rebuild of their dependents. Doing otherwise will lose much of the benefit provided by shared libraries. Also, if rebuilding dependents was a common practice, there wouldn't be such backslash against statically linked languages like Rust and Go.

@YaLTeR

@minoru @YaLTeR well, either that or soname stuff would be done to keep the program running, because if it's linked against a specific gtk version and that's no longer in the filesystem, things can break. And yeah, I'm imagining something like this happening because the rust and go model popularized this practice, but yeah, probably not.
@YaLTeR It is traditionally handled via shared libraries, and not having such static version-checks at all. That approach is going out of fad, especially in the Go/Rust eco-systems, which lead to Debian not covering Go/Rust with generic security support because the only way to fix it is to rebuild tons of packages. This is clearly not sustainable. Debian’s preference has been for Rust/Go to switch to shared libraries, but I think that effort is mostly lead by non-Go/Rust people.
@jas the problem I'm talking about is orthogonal to shared vs. static libraries
@jas actually you could even say it's *more* relevant when using shared libraries
@YaLTeR If you assume shared libraries, such build-time static checks doesn’t make sense do they? You need to make runtime-decisions, because at build time you don’t know which shared library will be used. And if you do runtime-checks, things ought to work regardless of version, and distributions doesn’t have to track this in any way. Still, there is a lot of hidden complexity (and source for weird bugs) with conditional code like this.

@YaLTeR @jas If you built the application with an older version of the library and it was working, this behavior is maintained, isn't it? It still uses the fallback as before.

Removing the fallback from the library would change the ABI, and soname would change, then package deps would no longer be satisfied, so a rebuild would be needed.

What you are asking for is runtime detection, which you'd need to know in advance and handle with dlopen.

Why does it sound like yet another Ubuntu problem?

@wolfpld @jas currently I have a more of a "call this new function that enables a new feature if library version is new enough to have the function, no other difference" case. While I can use dlopen() for this, it feels brittle to do it just for this one function, when I already properly dynamically link to the rest of the library. So I went with a build-time version check. Meaning that if a distro updates the library without rebuilding my app, the app won't get the new feature yeah
@wolfpld @jas a more complex case would be something like Ghostty which uses new Adw widgetry as the respective Adw versions are detected at build time. That's a whole set of new functions, I imagine would be annoying to dlsym and thread pointers everywhere
@YaLTeR, Nix and Guix handles this by rebuilding dependents whenever one of their dependency changes.
@cnx even on minor bumps?
@YaLTeR @cnx
Of course, even when one bit changes.
@YaLTeR @cnx Yep! Their deal is that builds must be reproducible and deterministic. Nix flakes often lock dependencies down to their source files' hashes.
@YaLTeR
openSUSE Buildservice rebuilds all dependent packages in this case. Making Tumbleweed allways consistent.
@vyskocilm even on minor bumps?
@YaLTeR
Yes, gtk 4.11 -> 4.12 would trigger the rebuild of all dependent packages in Tumbleweed.
@YaLTeR In Gentoo it is handled by subslot dependencies. When a dependency is updated to a higher subslot, the dependents that specify a subslot dependency are rebuilt.
@YaLTeR can't you just declare a cfg flag of your own that requires an upstream cfg feature? You can also codegen in a build script, although that's quite janky.
@Alonely0 yeah, I imagine you'd need to declare your own cfg flags (one per each version check that you need) and write build system integration that automatically sets them based on the build-time available library. Which is quite far from simple and easy, so nobody (including myself) does it
@YaLTeR fair point. I guess it would be nice to be able to cfg on dep's flags...
@YaLTeR There's a widely used crate cfg_if that allows turning the latter into an else branch. A variation of that in the standard library is approaching stabilization at <https://github.com/rust-lang/rust/issues/115585>.
Tracking issue for `cfg_select` (formerly `cfg_match`) · Issue #115585 · rust-lang/rust

Provides a native way to easily manage multiple conditional flags without having to rewrite each clause multiple times. Public API cfg_select! { unix => { fn foo() { /* unix specific functionality ...

GitHub
@chrysn I know about it but that's not the problematic part
@YaLTeR Ah, I focused on the wrong part. Indeed, the version-conditional aspect is annoying, and I don't know of any good efforts to make this better.

@YaLTeR it's some boilerplate and requires a build.rs but the code itself is easy?

https://doc.rust-lang.org/cargo/reference/build-script-examples.html#conditional-compilation

Build Script Examples - The Cargo Book

@robo9k hm, interesting. Could this perhaps somehow be developed further to where no build.rs is necessary on the consumer? Like, a way to set special cfgs directly?
@YaLTeR @robo9k indeed, having cfg-time access to dependency versions sounds like something important enough that should just work without any extra effort (and does in other languages). Cargo (if that's what's used) already knows which dependency versions it ended up selecting, why can't it set things up so that #[cfg(crate(somelib >= 1.2.3))] just works?