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

Enable ZMQ_HAVE_STRLCPY for GLIBC >= 2.38 #29

Merged
merged 14 commits into from
Aug 11, 2023
Merged

Enable ZMQ_HAVE_STRLCPY for GLIBC >= 2.38 #29

merged 14 commits into from
Aug 11, 2023

Conversation

jean-airoldie
Copy link
Owner

@jean-airoldie jean-airoldie commented Aug 4, 2023

Fixes #28

@MarijnS95 This approach checks glibc version. This results in better compilation speed than the CMAKE CheckCXXSymbolExists approach which involves compiling multiple times. Please tell me if it fixes your issue.

This should work for any platform that uses glibc.

Open questions:

src/lib.rs Outdated

pub(crate) fn from_libc() -> Self {
// SAFETY Call to libc via ffi.
let ptr = unsafe { libc::gnu_get_libc_version() };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to be checking libc on the current host that runs this "build script library", rather than testing if the headers/libraries intended for the target have a certain version?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does check the host libc version. In case of static linking this works fine. In case of dynamic linking, from what I recall, the binary will be compiled with the host's glibc version and should not run on the target unless it has a equal or newer version of glibc.

[...] rather than testing if the headers/libraries intended for the target have a certain version?

Isn't it the same? If you target gnu, your gonna need to compile with glibc.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm wrong and the host libc can really differ from the target libc version, then i think the approach is to compile and run a program that outputs the libc::gnu_get_libc_version.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case of static linking this works fine

In case of static linking your host has the static library of the target architecture, however;

then i think the approach is to compile and run a program that outputs the libc::gnu_get_libc_version.

This implies that your host can emulate the target architecture to run such a program.


The reason for the header check is because of the information about GLIBC in part comes from the sysroot.

Though afaik the sysroot/compiler is also available to set the GLIBC version and use older versions, in which case maybe the header emits the inline function as a fallback rather than the symbol? Or maybe older GLIBC header/library source is needed, I'm not too familiar with this.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a lot of experience with cross-compilation and building binaries that work in multiple glibc versions. I also worked in a project that runs on ARM and cross-compiles this crate. So this is my input:

  • glibc is forward-compatible. If you compile for a lower target glibc version, then the same binary works in systems with higher glibc version.
  • libc::gnu_get_libc_version gets the host glibc version, so cross-compilation will fail.
  • The classic workaround that autotools and cmake are using, is to actually invoke gcc and the source code will be a small C code that just calls function in question. If the compilation succeeds then the function is supported, otherwise it doesn't.

Based on the above the best approach is what I describe in the 3rd point.

Copy link

@oblique oblique Aug 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option is to use __GLIBC__ and __GLIBC_MINOR__ and get their values with cc::Build::expand.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oblique Thanks for confirming the above with additional context. Indeed, it should also be possible to extract the glibc version of te target sysroot via such environment variables.

The compilation test in C/C++ build-scripts is likely used in case a symbol is guarded by extra conditionals? Or more likely, to be interoperable across multiple libc standard library providers. Instead of hardcoding versions across platforms (for multiple symbols), this compile-test would for example run naturally on Android cross target instead of having to hardcode what we did in #20. Not saying that we should convert everything thus far to that though, but it's something to keep in mind.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the best is to be used when we are not sure. For example in Android we are sure that exists, so we don't need to check via compilation test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example in Android we are sure that exists, so we don't need to check via compilation test.

I haven't gone back very far at all though, but it seems very unlikely for anyone to go out of their way and build against even older versions of the NDK (which may still even contain the symbol).

@jean-airoldie
Copy link
Owner Author

@MarijnS95 I added a fallback for glibc dynamic compilation where we try to compile a simple c program linking against strlcpy in <string.h>.

@jean-airoldie
Copy link
Owner Author

@MarijnS95 Alright, now the lib does a compilation check to see if strlcpy is in the std lib, like you originally proposed. At least now I understand the reasoning. Could you please tell me if this fixes your issue, since I don't have an easy way to replicate it.

src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
Cargo.toml Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/strlcpy.c Outdated Show resolved Hide resolved
jean-airoldie and others added 2 commits August 7, 2023 18:00
Co-authored-by: Yiannis Marangos <[email protected]>
Co-authored-by: Yiannis Marangos <[email protected]>
@oblique
Copy link

oblique commented Aug 7, 2023

I tested the build on the latest glibc and works. Also a friend who had ready an environment for cross-compilation tested it and works there too,

@jean-airoldie
Copy link
Owner Author

@oblique Cool thanks for the help. I'll wait on @MarijnS95 to merge since he opened the issue.

Copy link
Contributor

@MarijnS95 MarijnS95 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me on Arch Linux with glibc 2.28, many thanks!

Just have some minor code-cleanup comments.

Note that I don't currently use a GNU cross-compiling environment but thought about pointing it out regardless. I currently only cross-compile to Android which had this hardcoded for a long time (#20), though it looks like @oblique already took the task of testing cross-compiling to GNU targets on them, thanks!

src/lib.rs Outdated
@@ -56,6 +56,24 @@ where
Err(())
}

#[cfg(target_env = "gnu")]
mod glibc {
use std::path::Path;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Path is already in scope in this file?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not against super imports, i just find them less explicit. In this case, I don't see the point since this is just one line.

env, fs,
fs::File,
env,
fs::{self, File},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I'm really against importing self and then a few other items into scope, rather then also qualifying them with e.g. fs::File if I really wanted to import fs into scope.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have some logical reasoning behind that? I don't really care about that stuff personally.

Comment on lines +63 to +65
// Attempt to compile a c program that links to strlcpy from the std
// library to determine whether glibc packages it.
pub(crate) fn has_strlcpy() -> bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is intended to be a (developer-only) doc comment:

Suggested change
// Attempt to compile a c program that links to strlcpy from the std
// library to determine whether glibc packages it.
pub(crate) fn has_strlcpy() -> bool {
/// Attempt to compile a c program that links to strlcpy from the [`std`]
/// library to determine whether glibc packages it.
pub(crate) fn has_strlcpy() -> bool {

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the benefit? It won't show up in the generated docs. Is a GUI code editor thing?

src/lib.rs Outdated
// Attempt to compile a c program that links to strlcpy from the std
// library to determine whether glibc packages it.
pub(crate) fn has_strlcpy() -> bool {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/strlcpy.c");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this pattern occurs more often, the CMake macro generates this file on the fly and doesn't even make any attempt to reproduce the signature faithfully.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. If in the future, if we need to do anything more complex, it would make sense to dynamically generate a file containing all the required links. For now I think its fine.

src/lib.rs Show resolved Hide resolved
src/lib.rs Outdated
Comment on lines 67 to 76
pub(crate) fn has_strlcpy() -> bool {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/strlcpy.c");

cc::Build::new()
.cargo_metadata(false)
.warnings_into_errors(true)
.file(path)
.try_compile("has_strlcpy")
.is_ok()
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just found the existence of get_compiler. With that we can actually link and create an executable, which is more correct. So this is better:

    pub(crate) fn has_strlcpy() -> bool {
        let c_src = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/strlcpy.c");
        let bin =
            PathBuf::from(env::var("OUT_DIR").unwrap()).join("has_strlcpy");

        println!("cargo:rerun-if-changed={}", c_src.display());

        cc::Build::new()
            .warnings(false)
            .get_compiler()
            .to_command()
            .arg(c_src)
            .arg("-o")
            .arg(bin)
            .status()
            .expect("failed to execute gcc")
            .success()
    }

You can also revert to the older comment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it need to link, given that the CMake scripts AFAIK don't do that either?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No we don't need to link as long as strlcpy.c produces only the warning we are interested in. If in the future gcc releases a new warning appears, then build will fail.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning flags might even get picked up from cc-rs-defined environment variables, skewing the result?

CMake does a bunch of tweaks to that:
https://github.com/Kitware/CMake/blob/master/Modules/CheckSymbolExists.cmake

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They also use the address of the symbol with a cast to support generic cases (something we don't need to do now) to not have to replicate the function signature.

Copy link

@oblique oblique Aug 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning flags might even get picked up from cc-rs-defined environment variables, skewing the result?

Yes. cc-rs is aware of CFLAGS and CFLAGS_<target> environment variables.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think linking is preferable to reduce the risk of false positives.

@jean-airoldie jean-airoldie merged commit 2cc6e19 into master Aug 11, 2023
@jean-airoldie
Copy link
Owner Author

@oblique @MarijnS95 Thanks again for your time. I'll do a release.

@s-nie
Copy link

s-nie commented Sep 15, 2023

Thanks a lot for the quick fix for this!

EDIT: Disregard the following. It looks like the sccache was not correctly invalidated. Works fine without it.

I noticed that the error still appears in cross compilation on Linux for Windows.

Any idea why this would be? I have a vague memory of platform checks not working as expected because the build script still compiles on Linux and then runs "on Windows," or something like that. So I sometimes have to check the platform with std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows").

The output of cargo xwin build --release --target x86_64-pc-windows-msvc in rust-zmq is:

  --- stderr
  /home/sven/.cargo/registry/src/index.crates.io-6f17d22bba15001f/zeromq-src-0.2.6+4.3.4/src/strlcpy.c(5,11): error: call to undeclared library function 'strlcpy' with type 'unsigned long long (char *, const char *, unsigned long long)'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
      (void)strlcpy(buf, "a", 1);
            ^
  /home/sven/.cargo/registry/src/index.crates.io-6f17d22bba15001f/zeromq-src-0.2.6+4.3.4/src/strlcpy.c(5,11): note: include the header <string.h> or explicitly provide a declaration for 'strlcpy'
  1 error generated.


  error occurred: Command "sccache" "clang-cl" "-nologo" "-MD" "-O2" "-Z7" "-Brepro" "-m64" "--target=x86_64-pc-windows-msvc" "-Wno-unused-command-line-argument" "-fuse-ld=lld-link" "/imsvc/home/sven/.cache/cargo-xwin/xwin/crt/include" "/imsvc/home/sven/.cache/cargo-xwin/xwin/sdk/include/ucrt" "/imsvc/home/sven/.cache/cargo-xwin/xwin/sdk/include/um" "/imsvc/home/sven/.cache/cargo-xwin/xwin/sdk/include/shared" "-I" "/home/sven/.cargo/registry/src/index.crates.io-6f17d22bba15001f/zeromq-src-0.2.6+4.3.4/vendor/include" "-I" "/home/sven/.cargo/registry/src/index.crates.io-6f17d22bba15001f/zeromq-src-0.2.6+4.3.4/vendor/src" "-I" "/home/sven/.cargo/registry/src/index.crates.io-6f17d22bba15001f/zeromq-src-0.2.6+4.3.4/vendor/src" "-I" "/home/sven/.cargo/registry/src/index.crates.io-6f17d22bba15001f/zeromq-src-0.2.6+4.3.4/vendor/external/sha1" "-I" "/home/sven/.cargo/registry/src/index.crates.io-6f17d22bba15001f/zeromq-src-0.2.6+4.3.4/vendor/external/wepoll" "-I" "/home/sven/.cargo/registry/src/index.crates.io-6f17d22bba15001f/zeromq-src-0.2.6+4.3.4/vendor/builds/deprecated-msvc" "/GL-" "/EHsc" "-DZMQ_BUILD_TESTS=OFF" "-DZMQ_USE_CV_IMPL_STL11=1" "-DZMQ_STATIC=1" "-DZMQ_USE_BUILTIN_SHA1=1" "-DZMQ_HAVE_WS=1" "-DZMQ_IOTHREAD_POLLER_USE_EPOLL=1" "-DZMQ_POLL_BASED_ON_POLL=1" "-D_WIN32_WINNT=0x0600" "-DZMQ_HAVE_IPC=1" "-Fo/home/sven/rust-zmq/target/x86_64-pc-windows-msvc/release/build/zmq-sys-5c768056df47cfca/out/lib/c7dc53c602ec3030-wepoll.o" "-c" "--" "/home/sven/.cargo/registry/src/index.crates.io-6f17d22bba15001f/zeromq-src-0.2.6+4.3.4/vendor/external/wepoll/wepoll.c" with args "clang-cl" did not execute successfully (status code exit status: 1).

@oblique
Copy link

oblique commented Sep 15, 2023

@s-nie Can you create an example repo that reproduces the issues?

@s-nie
Copy link

s-nie commented Sep 15, 2023

I updated the comment, it was an issue with sccache. Sorry for the confusion.

@oblique
Copy link

oblique commented Sep 15, 2023

I'm still confused :) Is there an issue when sccache is used in general? Or the issue was because of your setup?

@s-nie
Copy link

s-nie commented Sep 15, 2023

So I thought it was just an issue with cache invalidation in sccache. It keeping the pre-fix configuration around would have explained this. But I just tried again with sccache cleared and enabled, and the same issue came up again. My cargo configuration looks like this:

[build]
rustc-wrapper = "sccache"

The sccache version is 0.5.4.

Then, running cargo xwin build --release --target x86_64-pc-windows-msvc in rust-zmq should be enough to reproduce it, given the system has GLIBC 2.38.

@jean-airoldie
Copy link
Owner Author

jean-airoldie commented Sep 15, 2023

Would you mind creating a new separate issue, because this is hard to follow since this is a pull request thread.

Comment on lines +429 to +430
#[cfg(target_env = "gnu")]
if !has_strlcpy && glibc::has_strlcpy() {
Copy link
Contributor

@MarijnS95 MarijnS95 Sep 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this cfg checks the target_env of the host. Adding this snippet right above:

        panic!(
            "CARGO_CFG_TARGET_ENV: {:?} target_env=gnu?: {}",
            env::var("CARGO_CFG_TARGET_ENV"),
            cfg!(target_env = "gnu")
        );

And run with:

cargo b --target x86_64-pc-windows-msvc -p testcrate

Gives the following output:

  thread 'main' panicked at 'CARGO_CFG_TARGET_ENV: Ok("msvc") target_env=gnu?: true', src/lib.rs:429:9

confirms that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Should we define ZMQ_HAVE_STRLCPY for Linux?
4 participants