jeaye/ncurses-rs

Cannot crosscompile for armv7

vlisivka opened this issue · 8 comments

Cannot crosscompile ncurses-rs, because it compiles test program for armv7 and tries to execute it on x86_64. How to disable that?

[vlisivka@vlisivka ncurses-rs]$ TARGET_CC=/opt/scel/17.2/sysroots/x86_64-scelsdk-linux/usr/bin/arm-scel-linux-gnueabi/arm-scel-linux-gnueabi-gcc TARGET_CFLAGS='--sysroot=/opt/scel/17.2/sysroots/armv7ahf-neon-scel-linux-gnueabi -mcpu=cortex-a9 -mfpu=neon -mfloat-abi=hard -marm' RUST_BACKTRACE=1 cargo build --example ex_1 --target armv7-unknown-linux-gnueabihf 
   Compiling ncurses v5.99.0 (/home/vlisivka/tmp/rust-ui/ncurses-rs)
error: failed to run custom build command for `ncurses v5.99.0 (/home/vlisivka/tmp/rust-ui/ncurses-rs)`
process didn't exit successfully: `/home/vlisivka/tmp/rust-ui/ncurses-rs/target/debug/build/ncurses-b8678d1a3a7c29b3/build-script-build` (exit code: 101)
--- stdout
cargo:rerun-if-env-changed=PKG_CONFIG_PATH
cargo:rustc-link-lib=ncurses
OPT_LEVEL = Some("0")
TARGET = Some("armv7-unknown-linux-gnueabihf")
HOST = Some("x86_64-unknown-linux-gnu")
CC_armv7-unknown-linux-gnueabihf = None
CC_armv7_unknown_linux_gnueabihf = None
TARGET_CC = Some("/opt/scel/17.2/sysroots/x86_64-scelsdk-linux/usr/bin/arm-scel-linux-gnueabi/arm-scel-linux-gnueabi-gcc")
CFLAGS_armv7-unknown-linux-gnueabihf = None
CFLAGS_armv7_unknown_linux_gnueabihf = None
TARGET_CFLAGS = Some("--sysroot=/opt/scel/17.2/sysroots/armv7ahf-neon-scel-linux-gnueabi -mcpu=cortex-a9 -mfpu=neon -mfloat-abi=hard -marm")
CRATE_CC_NO_DEFAULTS = None
DEBUG = Some("true")
CARGO_CFG_TARGET_FEATURE = None

--- stderr
thread 'main' panicked at '/home/vlisivka/tmp/rust-ui/ncurses-rs/target/armv7-unknown-linux-gnueabihf/debug/build/ncurses-3eeeed0374790b74/out/chtype_size failed: Os { code: 8, kind: Other, message: "Exec format error" }', src/libcore/result.rs:997:5
stack backtrace:
   0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace
             at src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:39
   1: std::sys_common::backtrace::_print
             at src/libstd/sys_common/backtrace.rs:70
   2: std::panicking::default_hook::{{closure}}
             at src/libstd/sys_common/backtrace.rs:58
             at src/libstd/panicking.rs:200
   3: std::panicking::default_hook
             at src/libstd/panicking.rs:215
   4: std::panicking::rust_panic_with_hook
             at src/libstd/panicking.rs:478
   5: std::panicking::continue_panic_fmt
             at src/libstd/panicking.rs:385
   6: rust_begin_unwind
             at src/libstd/panicking.rs:312
   7: core::panicking::panic_fmt
             at src/libcore/panicking.rs:85
   8: core::result::unwrap_failed
             at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libcore/macros.rs:17
   9: <core::result::Result<T, E>>::expect
             at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libcore/result.rs:825
  10: build_script_build::check_chtype_size
             at ./build.rs:106
  11: build_script_build::main
             at ./build.rs:59
  12: std::rt::lang_start::{{closure}}
             at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/rt.rs:64
  13: std::panicking::try::do_call
             at src/libstd/rt.rs:49
             at src/libstd/panicking.rs:297
  14: __rust_maybe_catch_panic
             at src/libpanic_unwind/lib.rs:87
  15: std::rt::lang_start_internal
             at src/libstd/panicking.rs:276
             at src/libstd/panic.rs:388
             at src/libstd/rt.rs:48
  16: std::rt::lang_start
             at /rustc/fc50f328b0353b285421b8ff5d4100966387a997/src/libstd/rt.rs:64
  17: main
  18: __libc_start_main
             at ../csu/libc-start.c:308
  19: _start

Ah indeed, the current build.rs script compiles and execute a small program, which makes it hard to cross-compile it.

The absurd solution would be to install something like qemu-static with binfmt support - it lets the system run foreign binaries like here transparently. But as we'll soon see, it's not really what you want either.

The real solution is a bit trickier. Ncurses is a dynamically-linked library, but not all versions are ABI compatible. There is a bunch of options that can be enabled when building ncurses for the system, and as a result, when building a binary that links to ncurses, it's not guaranteed that it'll run seamlessly when linking the ncurses lib from another system.

For example, on 64-bits architectures, there are 2 possible sizes for the chtype type defined in ncurses.h. If the binary is compiled with a different size than the ncurses lib, then bad things happen.

Another possible difference is the version of the mouse API; different OS may ship ncurses libs with different version of the mouse API, even when the version of ncurses itself is the same. This is here again done with macro definitions in ncurses.h, and must match the version of the library being linked.

The first point doesn't apply on armv7 (it's 32-bits anyway), but the second point is still valid. It means that running a binary on a different system than the one it was built on is not safe in general. So when cross-compiling, we have no idea what options will be used on the target system.

The result is that we can't safely predict what will be compatible with the target system, but we could let the user override it - in which case we wouldn't need to try to guess it. This means when cross-compiling, you will need to somehow transmit some information about the target system: what is the size of a chtype, and what is the version of the mouse API (the only 2 options currently auto-detected). A chtype will probably be 32-bits on armv7, but the mouse API will depend on the OS installed (Ubuntu and Archlinux, for example, will ship ncurses libs with different options).

How to transmit this information? There are a few ways: we could use env variables, or rustc/cargo features... not sure which option is easiest to integrate in a larger build (when ncurses is used as a dependency of a dependency...).

To detect type size at compile time this trick can be used:

int main()
{
    int dummy;
    int a_type;

    switch (dummy) {
    case 4:
    case sizeof(a_type):
        break;
    }
    return 0;
}

Program will not compile if int size is 4 bytes.

Same approach can be used to detect mouse API version: some programs will compile, some not. Just generate a sample program and then test is it compilable, like autoconf does.

But the problem remains: it only tests the host ncurses config, not the target.

If you will compile sample program using target compiler, then it will be tested with target ncurses config.

TARGET_CC = Some("/opt/scel/17.2/sysroots/x86_64-scelsdk-linux/usr/bin/arm-scel-linux-gnueabi/arm-scel-linux-gnueabi-gcc")
TARGET_CFLAGS = Some("--sysroot=/opt/scel/17.2/sysroots/armv7ahf-neon-scel-linux-gnueabi -mcpu=cortex-a9 -mfpu=neon -mfloat-abi=hard -marm")

I commented out check_chtype_size(&ncurses_lib); and examples compiled fine and works fine on armv7 linux (Yocto), except for unicode, but Cursive examples are unable to display pseudo-graphics characters, maybe because ncurses thinks that unicode is not supported.

jansc commented

I'm having a similar problem when trying to create a package with TARGET x86_64-unknown-linux-musl on travis-ci:

error: failed to run custom build command for `ncurses v5.99.0`
Caused by:
process didn't exit successfully: `/home/travis/build/jansc/ncgopher/target/release/build/ncurses-91d5844f60f28ff9/build-script-build` (exit code: 101)
--- stdout

cargo:rerun-if-env-changed=PKG_CONFIG_PATH
cargo:rustc-link-lib=ncursesw
OPT_LEVEL = Some("3")
TARGET = Some("x86_64-unknown-linux-musl")
HOST = Some("x86_64-unknown-linux-gnu")
CC_x86_64-unknown-linux-musl = None
CC_x86_64_unknown_linux_musl = None
TARGET_CC = None
CC = None
CROSS_COMPILE = None
CFLAGS_x86_64-unknown-linux-musl = None
CFLAGS_x86_64_unknown_linux_musl = None
TARGET_CFLAGS = None
CFLAGS = None
CRATE_CC_NO_DEFAULTS = None
DEBUG = Some("false")
CARGO_CFG_TARGET_FEATURE = Some("crt-static,fxsr,sse,sse2")

--- stderr
/home/travis/build/jansc/ncgopher/target/x86_64-unknown-linux-musl/release/build/ncurses-b21f308415d32a8b/out/chtype_size.c:6:21: fatal error: ncurses.h: No such file or directory

compilation terminated.

thread 'main' panicked at 'assertion failed: command.status().expect("compilation failed").success()', /home/travis/.cargo/registry/src/github.com-1ecc6299db9ec823/ncurses-5.99.0/build.rs:105:5```

Any updates on this?

I have the same exact problem with --target=aarch64-unknown-linux-gnu

  TARGET = Some("aarch64-unknown-linux-gnu")
  HOST = Some("x86_64-unknown-linux-musl")
  cargo:rerun-if-env-changed=CC_aarch64-unknown-linux-gnu
  CC_aarch64-unknown-linux-gnu = None
  cargo:rerun-if-env-changed=CC_aarch64_unknown_linux_gnu
  CC_aarch64_unknown_linux_gnu = None
  cargo:rerun-if-env-changed=TARGET_CC
  TARGET_CC = Some("aarch64-oe-linux-gcc  -mcpu=cortex-a53 -march=armv8-a+crc+crypto --sysroot=/opt/oecore-x86_64-kirkstone/sysroots/cortexa53-crypto-oe-linux")
  cargo:rerun-if-env-changed=CFLAGS_aarch64-unknown-linux-gnu
  CFLAGS_aarch64-unknown-linux-gnu = None
  cargo:rerun-if-env-changed=CFLAGS_aarch64_unknown_linux_gnu
  CFLAGS_aarch64_unknown_linux_gnu = None
  cargo:rerun-if-env-changed=TARGET_CFLAGS
  TARGET_CFLAGS = Some(" -O2 -pipe -g -feliminate-unused-debug-types ")
  cargo:rerun-if-env-changed=CRATE_CC_NO_DEFAULTS
  CRATE_CC_NO_DEFAULTS = None
  DEBUG = Some("false")
  CARGO_CFG_TARGET_FEATURE = Some("aes,crc,llvm14-builtins-abi,neon,pmuv3,sha2")

  --- stderr
  thread 'main' panicked at '/(....)/target/aarch64-unknown-linux-gnu/release/build/ncurses-e40bbafe95398f15/out/chtype_size failed: Os { code: 8, kind: Uncategorized, message: "Exec format error" }', /src/.cargo/registry/src/github.com-1ecc6299db9ec823/ncurses-5.101.0/build.rs:107:10