In Rust eco­sys­tem it is fairly pop­u­lar for a FFI bind­ing lib­rary de­clare the “nat­ive” lib­rar­ies it links to in a build.rs script. If the bind­ing is in­ten­ded for a cross-­plat­form use, chances are that the build.rs script is writ­ten in­cor­rectly.

As­sume a some­what com­mon case where link­age de­cisions are plat­form de­pend­ent. One then might write the build.rs script as such:

This will work great if the target = host . However, it is not a case in a cross-­com­pil­a­tion scen­ario, when target is not the same as the host .

Con­sider target = windows and host = linux for ex­ample. This is what would hap­pen, when one is cross-­com­pil­ing us­ing the mingw tool­chain from a linux sys­tem. In this scen­ario, the build.rs script runs on the ma­chine which does the com­pil­a­tion, so it is com­piled for linux and the con­fig­ur­a­tion vari­ables as­sume val­ues typ­ical of a linux tar­get. This means that the build.rs script in ques­tion will out­put the lib­rar­ies for #[cfg(not(windows))] , rather than #[cfg(windows)] case… but we’re tar­get­ing Win­dows and want the Win­dows lib­rar­ies! This ob­vi­ously can’t work! What a mess!

That’s ex­actly the bug I had to solve in lib­load­ing . The libloading lib­rary ex­poses a cross-­plat­form API for dy­nam­ic­ally load­ing (and un­load­ing) lib­rar­ies. Rel­ev­ant sys­tem APIs, on UNIX-­like sys­tems come in a form of dlopen , dlclose , dlsym , et cet­era. These, as it turns out, are provided by dif­fer­ent lib­rar­ies on dif­fer­ent sys­tems. On Linux-­likes it comes from libdl, FreeBSD et al provide it in libc, whereas vari­ants of OpenBSD will make these sym­bols avail­able in any dy­namic ex­ecut­able, no link­ing in­volved. To en­able such con­di­tional reas­on­ing in build.rs scripts I had re­sor­ted to writ­ing tar­get_build_utils, which would go as far as to rep­lic­ate the rustc be­ha­viour and even parse the cus­tom tar­get spe­cific­a­tions.

Sadly, tar­get_build_utils is not the nicest lib­rary in the world as it pulls along quite a num­ber of heavy de­pend­en­cies. To every­body’s re­joice, since the last time I worked on this… some­time between Rust ver­sion 1.13 and 1.14… Cargo began ex­port­ing some un­doc­u­mented, but very use­ful, en­vir­on­ment vari­ables dur­ing the ex­e­cu­tion of the build.rs scripts:

CARGO_CFG_TARGET_OS ;

; CARGO_CFG_TARGET_ENV ;

; CARGO_CFG_TARGET_FEATURE ;

; CARGO_CFG_TARGET_ENDIAN ;

; CARGO_CFG_TARGET_VENDOR ;

; CARGO_CFG_TARGET_FAMILY ;

; CARGO_CFG_DEBUG_ASSERTIONS ;

; CARGO_CFG_TARGET_HAS_ATOMIC ;

; CARGO_CFG_TARGET_POINTER_WIDTH ;

; CARGO_CFG_TARGET_THREAD_LOCAL ;

; CARGO_CFG_UNIX ;

; CARGO_CFG_TARGET_ARCH ;

These vari­ables cor­res­pond to equi­val­ent cfg(...) at­trib­utes in the source code and are oth­er­wise ex­actly what it says on the la­bel. The dif­fer­ence from the reg­u­lar cfg(...) at­trib­utes lies in these vari­ables as­sum­ing val­ues for the tar­get sys­tem, rather than the host sys­tem. This makes it pos­sible to cor­rectly handle the cross-­com­pil­a­tion scen­ario without re­sort­ing to lib­rar­ies like tar­get_build_utils. By us­ing these vari­ables, it is pos­sible to write a build.rs that’s cor­rect in the cross-­com­pil­a­tion scen­ario de­scribed above. Fol­low­ing code snip­pet is how a cor­rect build.rs script might end up look­ing like:

Glad to see the tool­ing im­prov­ing at such a break­neck pace. Cheers for ever im­prov­ing cross-­com­pil­a­tion story in Rust!