Cross-build crystal compiler but without build libgc.a? (but lib/crystal/libgc.a exists in offical released tarball)

As i asked before, i wrote a Dockerfile for cross compile Crystal compiler, it work quite well.

Dockerfile

-- mode: dockerfile-ts; --

确保 build 和 linking 是同一个 alpine 版本

ARG alpine_version=3.19

mirrors.ustc.edu.cn mirrors.tuna.tsinghua.edu.cn

ARG alpine_mirror=mirrors.ustc.edu.cn
ARG llvm_version=17

=============== 使用本地主机相同的架构 cross compile ===============

如果这里不指定 --platform, 则 docker 会自动选择根据命令行 --platform 指定的架构

这里强制使用当前 build 所在平台的架构,来高性能的 cross compile.

FROM --platform=$BUILDPLATFORM alpine:$alpine_version AS base

ARG alpine_mirror
ARG llvm_version
RUN sed -i “s/dl-cdn.alpinelinux.org/$alpine_mirror/g” /etc/apk/repositories

Add trusted CAs for communicating with external services and required build tooling

额外添加了 -dev 包,也允许编译动态版本。

crystal 依赖:libevent-dev libevent-static llvm16-libs musl-dev pcre2-dev gc-dev gcc

RUN set -eux;
apk add
–update
ca-certificates
yaml-dev
openssl-dev
zlib-dev
libunwind-dev
libssh2-dev
lz4-dev
sqlite-dev
libxml2-dev
tzdata
crystal shards
g++ make automake libtool autoconf
llvm${llvm_version}-dev
;

为了 https 的情况下,将证书一起部署可以工作,这个似乎是必须的。

RUN update-ca-certificates

RUN addgroup -g 1000 docker &&
adduser -u 1000 -G docker -h /home/docker -s /bin/sh -D docker

USER docker:docker

WORKDIR 应该放在 USER 后面,因为如果 /app 不存在,需要使用 user docker 创建这个文件夹

WORKDIR /app

FROM base AS build_cross_platform

ARG TARGETARCH

千万不要将这个变量名改为大写的 FLAGS, 因为 make 编译的时候,会识别这个变量,它潜在影响的包很多,造成奇怪的编译错误。

ARG flags=“–release --no-debug --cross-compile --target=$TARGETARCH --link-flags=-Wl,-L/app --link-flags=-s”

ARG project_name=crystal-lang/shards
RUN wget -O - https://github.com/$project_name/releases/latest |grep -E -o “$project_name/releases/tag/[^"]*" data-view-component” |cut -d’“’ -f1 |rev|cut -d’/’ -f1 |rev > shards_version
RUN mkdir -p shards && cd shards &&
git clone --depth=1 --single-branch --branch=$(cat /app/shards_version) GitHub - crystal-lang/shards: Dependency manager for the Crystal language . &&
make bin/shards static=1 FLAGS=”$flags" | grep '^cc ’ |tee shards.sh

COPY --chown=docker:docker . .

RUN make clean && rm -f app.sh &&
make crystal stats=1 static=1 FLAGS=“$flags” |grep '^cc ’ |tee app.sh &&
DESTDIR=/app/tmp make install &&
rm -f src/llvm/ext/llvm_ext.o &&
make docs

SHELL [“/bin/ash”, “-eo”, “pipefail”, “-c”]

============================== 使用目标系统架构 link binary ==============================

FROM alpine:$alpine_version as link_target

ARG alpine_mirror
ARG llvm_version
RUN sed -i “s/dl-cdn.alpinelinux.org/$alpine_mirror/g” /etc/apk/repositories

Add trusted CAs for communicating with external services and required build tooling

g++ 依赖 libc-dev, libc-dev 依赖 musl-dev

llvm17-dev 依赖 libffi-dev

RUN set -eux;
apk add
–update
gc-dev pcre2-dev
libevent-static
sqlite-static
openssl-libs-static
yaml-static
zlib-static
zstd-static
libxml2-static
llvm${llvm_version}-dev llvm${llvm_version}-static
g++ make automake libtool autoconf
;

RUN addgroup -g 1000 docker &&
adduser -u 1000 -G docker -h /home/docker -s /bin/sh -D docker

USER docker:docker

WORKDIR 应该放在 USER 后面,因为如果 /app 不存在,需要创建这个文件夹

WORKDIR /app

All new files and directories are created with a UID and GID of 0, unless the optional --chown flag

COPY --from=build_cross_platform --chown=docker:docker /app/Makefile .
COPY --from=build_cross_platform --chown=docker:docker /app/src/llvm/ext src/llvm/ext
COPY --from=build_cross_platform --chown=docker:docker /app/.build .build
COPY --from=build_cross_platform --chown=docker:docker /app/tmp tmp
COPY --from=build_cross_platform --chown=docker:docker /app/app.sh .
COPY --from=build_cross_platform --chown=docker:docker /app/shards shards

RUN cd shards && sh -ex shards.sh

RUN make llvm_ext

RUN sh -ex app.sh

RUN cp -v .build/crystal shards/bin/shards tmp/usr/local/bin/ &&
cp -v src/llvm/ext/llvm_ext.o tmp/usr/local/share/crystal/src/llvm/ext/llvm_ext.o

Build a minimal docker image

FROM scratch AS mini
WORKDIR /
ENV PATH=$PATH:/

This is your application

COPY --from=link_target /app/tmp/usr/local /
COPY --from=build_cross_platform /app/docs /docs

USER docker:docker

# Spider-gazelle has a built in helper for health checks (change this as desired for your applications)

HEALTHCHECK CMD [“/app”, “-c”, “http://127.0.0.1:3000/”]

Run the app binding on port 3000

EXPOSE 3000

ENTRYPOINT [“/app/bin/app”]

CMD [“/app”, “-b”, “0.0.0.0”, “-p”, “3000”]

But i found when i built done, there is no a libgc.a generated, in fact, no this file, it’ seem like just work well.

In fact, when I asked this question, I discovered following difference.

# download from github release

 ╰─ $ ./crystal version
Crystal 1.11.1 [0aa30372c] (2024-01-11)

LLVM: 15.0.7
Default target: x86_64-unknown-linux-gnu

 ╭─ 03:48  zw963 ⮀ ~/Downloads/121_18593/crystal-1.11.1-1/bin ⮀ ➦ ruby-3.2.1 
 ╰─ $ file crystal 
crystal: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped

 ╰─ $ ls -l crystal
.rwxr-xr-x 80M zw963 5 hours crystal*

# compile by myself

 ╰─ $ ./crystal version
Crystal 1.11.1 [0aa30372c] (2024-01-11)

LLVM: 17.0.5
Default target: x86_64-alpine-linux-gnu

 ╭─ 03:47  zw963 ⮀ ~/Crystal/crystal-lang/crystal/linux/amd64/bin ⮀ ⭠ (0aa30372c) (1.11.1) %  ➦ ruby-3.2.1 
 ╰─ $ file crystal 
crystal: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=18225567b6b92a048d788a393df83aaf07d36d50, stripped

 ╰─ $ ls -l crystal
.rwxr-xr-x 93M zw963 50 minutes crystal*

My question is:

  1. What is libgc.a used for? i select target=x86_64-linux-musl when built, but generated bianry give Default target: x86_64-alpine-linux-gnu, Why?
  2. is libgc.a only required for static compilation using linux-gnu(instead of linux-musl) target?
  3. How to built libgc.a if it is necessory?
  4. myself build file size is 93M, but offical is 80M, both use same Makefile with --release, The size difference is a little big?
  5. why offical build no info like BuildID[sha1]=18225567b6b92a048d788a393df83aaf07d36d50?

There aren’t many newer discussions about build/cross-compile on forum, and I haven’t found the answer, thanks in advance for spending your time reading/answer my question just for my curiosity!

That is due the version of LLVM and/or GCC, as they introduce this information, computed by the object files when linking.

This was not present in previous versions.

It depends on the Linux distro you’re using. Alpine Linux provides libgc.a which is the static version, so linking to it will do static linking.

Other distributions like Ubuntu might have a libgc.a file but that is just links your executable to the .so file.

Depends on the Linux distribution.

Is Boehm GC, the memory manager/garbage collector library used by Crystal.

No, you will need that when compiling Crystal applications, along the source code of Crystal itself.

Cheers.

Thanks for you excellent answer.

Really? i just compile several little project use (myself)static compiled version crystal it’ seem like there is no need for libgc.a?

 ╰─ $ cr version
Crystal 1.11.1 [0aa30372c] (2024-01-11)

LLVM: 15.0.7
Default target: x86_64-alpine-linux-gnu

 ╭─ 20:12  zw963 ⮀ ~/Crystal/crystal-china/procodile_cr ⮀ ⭠ (7a61fa2) master $%  ➦ ruby-3.2.1 
 ╰─ $ file ~/Crystal/crystal_static/bin/crystal
/home/zw963/Crystal/crystal_static/bin/crystal: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped

 ╭─ 20:12  zw963 ⮀ ~/Crystal/crystal-china/procodile_cr ⮀ ⭠ (7a61fa2) master $%  ➦ ruby-3.2.1 
 ╰─ $ ls -l ~/Crystal/crystal_static/bin/crystal
.rwxr-xr-x 80M zw963 16 minutes /home/zw963/Crystal/crystal_static/bin/crystal*

I can ensure libgc.a not exists in /usr or any Crystal installed folder, but i build several project both successful use this static version Crystal.

BTW: @luislavena , I just happened to be watching your video about zig cc, it help me a lot.

I just used zig cc to successfully cross build a musl version of luagit on Arch linux.

zig cc logs
╰─ $ make CC="zig cc -target x86_64-linux-musl" HOST_CC="zig cc" TARGET_STRIP="echo" LDFLAGS='-lunwind'
==== Building LuaJIT 2.0 ====
make -C src
make[1]: Entering directory '/home/zw963/Crystal/play/zigcc/LuaJIT/src'
HOSTCC    host/minilua.o
HOSTCC    host/buildvm_asm.o
HOSTCC    host/buildvm_peobj.o
HOSTCC    host/buildvm_lib.o
CC        lj_gc.o
HOSTCC    host/buildvm_fold.o
CC        lj_char.o
CC        lj_obj.o
CC        lj_str.o
CC        lj_tab.o
CC        lj_func.o
CC        lj_udata.o
CC        lj_meta.o
CC        lj_debug.o
CC        lj_state.o
CC        lj_vmevent.o
LLD Link... LLD Link... LLD Link... LLD Link... LLD Link... CC        lj_vmmath.o
CC        lj_strscan.o
CC        lj_api.o
CC        lj_lex.o
LLD Link... CC        lj_parse.o
CC        lj_bcread.o
CC        lj_load.o
CC        lj_bcwrite.o
LLD Link... CC        lj_ir.o
LLD Link... CC        lj_opt_mem.o
CC        lj_opt_narrow.o
CC        lj_opt_dce.o
LLD Link... LLD Link... LLD Link... LLD Link... LLD Link... LLD Link... CC        lj_opt_loop.o
CC        lj_opt_split.o
CC        lj_opt_sink.o
LLD Link... CC        lj_mcode.o
LLD Link... CC        lj_snap.o
LLD Link... CC        lj_asm.o
CC        lj_trace.o
LLD Link... LLD Link... CC        lj_gdbjit.o
CC        lj_ctype.o
CC        lj_cdata.o
CC        lj_cconv.o
LLD Link... CC        lj_ccall.o
LLD Link... CC        lj_ccallback.o
LLD Link... CC        lj_carith.o
CC        lj_clib.o
CC        lj_cparse.o
LLD Link... CC        lj_lib.o
CC        lj_alloc.o
LLD Link... CC        lib_aux.o
LLD Link... LLD Link... CC        lib_package.o
LLD Link... LLD Link... CC        lib_init.o
HOSTLINK  host/minilua
LLD Link... VERSION   luajit.h
DYNASM    host/buildvm_arch.h
CC        luajit.o
LLD Link... HOSTCC    host/buildvm.o
HOSTLINK  host/buildvm
BUILDVM   lj_vm.s
BUILDVM   lj_recdef.h
BUILDVM   lj_folddef.h
BUILDVM   lj_bcdef.h
BUILDVM   lj_ffdef.h
BUILDVM   lj_libdef.h
BUILDVM   jit/vmdef.lua
ASM       lj_vm.o
CC        lj_err.o
CC        lj_dispatch.o
CC        lj_record.o
CC        lj_ffrecord.o
CC        lib_base.o
CC        lj_crecord.o
CC        lib_bit.o
CC        lib_math.o
CC        lib_string.o
CC        lib_table.o
CC        lib_io.o
LLD Link... LLD Link... CC        lib_os.o
CC        lib_debug.o
LLD Link... LLD Link... CC        lib_jit.o
CC        lib_ffi.o
LLD Link... CC        lj_bc.o
LLD Link... LLD Link... LLD Link... CC        lj_opt_fold.o
AR        libluajit.a
DYNLINK   libluajit.so
libluajit.so
LINK      luajit
luajit
OK        Successfully built LuaJIT
make[1]: Leaving directory '/home/zw963/Crystal/play/zigcc/LuaJIT/src'
==== Successfully built LuaJIT 2.0 ====
1 Like

libgc.a is used when linking your executable. Most likely that is available in your environment and used by the linking step of crystal build

It is not required at runtime, since is an static library and all functions it provides were already integrated inside the executable (by the linking process).

If you didn’t had libgc.a when doing crystal build, it would have failed to link with something like this:

$ crystal version
Crystal 1.11.1 (2024-01-11)

LLVM: 17.0.5
Default target: aarch64-alpine-linux-musl

$ cat hello.cr
puts "Hello!"

$ crystal build hello.cr

$ ldd hello
	/lib/ld-musl-aarch64.so.1 (0xffffa28bb000)
	libpcre2-8.so.0 => /usr/lib/libpcre2-8.so.0 (0xffffa26d9000)
	libgc.so.1 => /usr/lib/libgc.so.1 (0xffffa265a000)
	libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0xffffa2629000)
	libc.musl-aarch64.so.1 => /lib/ld-musl-aarch64.so.1 (0xffffa28bb000)

$ crystal build hello.cr --static

$ ldd hello
/lib/ld-musl-aarch64.so.1: hello: Not a valid dynamic program

$ rm /usr/lib/libgc.a

$ crystal build hello.cr --static
/usr/lib/gcc/aarch64-alpine-linux-musl/13.2.1/../../../../aarch64-alpine-linux-musl/bin/ld: cannot find -lgc (this usually means you need to install the development package for libgc): No such file or directory
collect2: error: ld returned 1 exit status
Error: execution of command failed with exit status 1: cc "${@}" -o /tmp/hello  -rdynamic -static -L/usr/local/bin/../lib/crystal -lpcre2-8 -lgc -lpthread -ldl -levent

Hi, thanks for explain.

The above command(with --static) not work on arch linux.

 ╰─ $ crystal build hello.cr --static
/usr/sbin/ld: cannot find -lgc (this usually means you need to install the development package for libgc): No such file or directory
/usr/sbin/ld: cannot find -levent (this usually means you need to install the development package for libevent): No such file or directory
collect2: error: ld returned 1 exit status
Error: execution of command failed with exit status 1: cc "${@}" -o /home/zw963/Crystal/crystal-china/procodile_cr/hello  -rdynamic -static -L/home/zw963/Crystal/crystal_static/bin/../lib/crystal -lpcre2-8 -lgc -lpthread -ldl -lpthread -levent -lrt -lpthread -ldl

I consider this result is expected for the current Crystal compiler for linux, --static only work on alpine linux.


For the libgc.a, it is not available for crystal build on my laptop (arch linux)

 ╰─ $ sudo updatedb -e /home/.snapshots
 ╰─ $ locate 'libgc.a'
/home/zw963/Crystal/lib1/crystal/libgc.a
/home/zw963/Crystal/play/magic-haversack/lib/aarch64-linux-musl/libgc.a
/home/zw963/Crystal/play/magic-haversack/tmp/ports/aarch64-linux-musl/gc/8.2.2/usr/lib/libgc.a
 ╰─ $ echo $CRYSTAL_OPTS 
-Dstrict_multi_assign -Dno_number_autocast -Duse_pcre2 -Dpreview_overload_order
 ╰─ $ rm -rf ~/.cache/crystal
 ╭─ 16:04  zw963 ⮀ ~/Crystal/crystal-china/procodile_cr ⮀ ⭠ (7a61fa2) master $  ➦ ruby-3.2.1 
 ╰─ $ git clean -fdx
Removing bin/
Removing lib/

 ╰─ $ which -a crystal
/home/zw963/Crystal/crystal_static/bin/crystal
/home/zw963/Crystal/bin/crystal
/usr/sbin/crystal
/sbin/crystal
/usr/bin/crystal
 ╰─ $ file /home/zw963/Crystal/crystal_static/bin/crystal
/home/zw963/Crystal/crystal_static/bin/crystal: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped

 ╰─ $ rm -f shards.lock && shards install
Resolving dependencies
Fetching https://github.com/crystal-ameba/ameba.git
Installing ameba (1.6.1)
Postinstall of ameba: shards build -Dpreview_mt
Writing shard.lock

 ╰─ $ shards build
Dependencies are satisfied
Building: procodile
In src/procodile/cli.cr:99:29

 99 | process = ::Process.fork do
                          ^---
Warning: Deprecated Process.fork. Fork is no longer supported.

A total of 1 warnings were found.

 ╰─ $ shards build --release
Dependencies are satisfied
Building: procodile
In src/procodile/cli.cr:99:29

 99 | process = ::Process.fork do
                          ^---
Warning: Deprecated Process.fork. Fork is no longer supported.

A total of 1 warnings were found.

 ╰─ $ bin/procodile 
Welcome to Procodile v1.0.19
For documentation see https://adam.ac/procodile.

The following commands are supported:

  check_concurrency  Check process concurrency
  console            Open a console within the environment
  exec               Execute a command within the environment
  help               Shows this help output
  kill               Forcefully kill all known processes
  log                Open/stream a Procodile log file
  reload             Reload Procodile configuration
  restart            Restart processes
  run                Execute a command within the environment
  start              Starts processes and/or the supervisor
  status             Show the current status of processes
  stop               Stops processes and/or the supervisor

For details for the options available for each command, use the --help option.
For example 'procodile start --help'.

BTW, for the compiler file size and build info, i have verified, it probably because different llvm version.

Following binary i built use alpine 3.19

Following biary i built use offical crystallang/crystal:latest-alpine

 ╰─ $ ./crystal version
Crystal 1.11.1 [0aa30372c] (2024-01-11)

LLVM: 15.0.7
Default target: x86_64-alpine-linux-gnu

 ╭─ 16:19  zw963 ⮀ ~/Crystal/crystal_static/bin ⮀ ➦ ruby-3.2.1 
 ╰─ $ ls -l crystal 
.rwxr-xr-x 80M zw963 20 hours crystal*

 ╰─ $ file crystal 
crystal: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, strippe

Following binary i built use alpine 3.19 with llvm 17.

 ╰─ $ ./crystal version
Crystal 1.11.1 [0aa30372c] (2024-01-11)

LLVM: 17.0.5
Default target: x86_64-alpine-linux-gnu

 ╰─ $ ls -l crystal
.rwxr-xr-x 93M zw963 3 minutes crystal*

 ╰─ $ file crystal 
crystal: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=18225567b6b92a048d788a393df83aaf07d36d50, stripped

when use llvm 17, you can see the buildID info, and file size increased by 19 MB


But, i don’t know why, the built result always show x86_64-alpine-linux-gnu, instead of x86_64-alpine-linux-musl, even when i select the build with --target=x86_64-linux-musl.

Do /usr/lib/libevent.a and /usr/lib/libgc.a exist? (or /usr/lib64/ if it’s like Slackware) Those would be the needed static libraries.

Some distros don’t ship static libraries. Like Slackware ships almost NO static libraries at all, just dynamic libraries plus a small number of static libraries, so I’ve never been able to compile static binaries.

Well unless I want to rebuild some very core system packages…

No.

 ╰─ $ locate 'libgc.a'
/home/zw963/Crystal/lib1/crystal/libgc.a
/home/zw963/Crystal/play/magic-haversack/lib/aarch64-linux-musl/libgc.a
/home/zw963/Crystal/play/magic-haversack/tmp/ports/aarch64-linux-musl/gc/8.2.2/usr/lib/libgc.a

 ╰─ $ locate 'libevent.a'
/home/zw963/Crystal/lib1/crystal/lib/libevent.a
/home/zw963/Crystal/play/magic-haversack/lib/aarch64-linux-musl/libevent.a
/home/zw963/Crystal/play/magic-haversack/tmp/ports/aarch64-linux-musl/libevent/2.1.12/usr/lib/libevent.a

as you can see, those exists file, except come from magic-haversack, the left is download from offical tarball. modified one year ago.

 ╭─ 21:08  zw963 ⮀ ~/Crystal/lib1/crystal ⮀ ➦ ruby-3.2.1 
 ╰─ $ eza -Tl
drwxr-xr-x    - zw963 1 year ./
lrwxrwxrwx    - zw963 1 year ├── bin -> ../../bin/
drwxr-xr-x    - zw963 1 year ├── lib/
.rw-r--r-- 652k zw963 1 year │  ├── libevent.a
.rw-r--r-- 5.1k zw963 1 year │  ├── libevent_pthreads.a
.rw-r--r-- 727k zw963 1 year │  └── libpcre.a
.rw-r--r-- 378k zw963 1 year └── libgc.a

But, i am not use the Crystal from ~/Crystal/bin, in fact, i change folder name from lib to lib1.


BTW, i used to use Slackware too, in fact, it is my first linux distro since i switch from windows about 10 years ago. I use Slackware/Salix linux for a long time, then switch to Arch since 3~4 years ago, anyway, Slackware user rarely seen nowaday.

@zw963 what are the problem you’re trying to solve?

I’ve tried to understand the first steps from your Dockerfile and I’m confused and a bit lost, but bear with me for a moment.

You’re saying that libgc.a appears not to be needed (and is not even referenced by Crystal when compiling your applications).

You could check and prove what gets needs during the compiling of your applications by overriding CC env variable, which will be used by Crystal when linking your executable:

/tmp $ CC=echo crystal build hello.cr
_main.o0.o S-lice40U-I-nt841.o0.o ... U-I-nt8.o0.o ... -o /tmp/hello -rdynamic -L/usr/local/bin/../lib/crystal -lpcre2-8 -lgc -lpthread -ldl -levent

Focus on this:

-rdynamic -L/usr/local/bin/../lib/crystal -lpcre2-8 -lgc -lpthread -ldl -levent

And when doing static:

/tmp $ CC=echo crystal build --static hello.cr
_main.o0.o S-lice40U-I-nt841.o0.o ... C-rystal.o0.o -o /tmp/hello -rdynamic -static -L/usr/local/bin/../lib/crystal -lpcre2-8 -lgc -lpthread -ldl -levent

We now have this:

-rdynamic -static -L/usr/local/bin/../lib/crystal -lpcre2-8 -lgc -lpthread -ldl -levent

-lgc indicates _links against gc, being that libgc.a or depending on the compiler, automatically libgc.so, even when there is no .a file.

If you still see -lgc there and you still get an executable, that means libgc.a is being picked up from somewhere in the library lookup path, for which crystal env should help you see the paths used to link against.

Cheers.

Yes guess this is the answer, the compiler pick the libgc.so in the gc package (as gc-dev package in alpine), this is a basic package required by guile, which required by make, i even can’t remove it because dependencies on a newly installed Arch system, so, i guess i don’t need libgc.a

 ╰─ $ pacman -Qi gc
Name            : gc
Version         : 8.2.4-1
Description     : A garbage collector for C and C++
Architecture    : x86_64
URL             : https://www.hboehm.info/gc/
Licenses        : GPL
Groups          : None
Provides        : None
Depends On      : gcc-libs
Optional Deps   : None
Required By     : crystal  guile
Optional For    : None
Conflicts With  : None
Replaces        : None
Installed Size  : 769.74 KiB
Packager        : Frederik Schwan <freswa@archlinux.org>
Build Date      : Sat 27 May 2023 08:16:00 PM CST
Install Date    : Sun 03 Dec 2023 04:23:40 PM CST
Install Reason  : Installed as a dependency for another
                  package
Install Script  : No
Validated By    : Signature

 ╰─ $ pacman -R gc
checking dependencies...
error: failed to prepare transaction (could not satisfy dependencies)
:: removing gc breaks dependency 'gc' required by guile

 ╰─ $ 1  pacman -R guile
checking dependencies...
error: failed to prepare transaction (could not satisfy dependencies)
:: removing guile breaks dependency 'guile' required by make

 ╰─ $ pacman -Ql 
gc /usr/
gc /usr/include/
gc /usr/include/gc.h
gc /usr/include/gc/
gc /usr/include/gc/cord.h
gc /usr/include/gc/cord_pos.h
gc /usr/include/gc/ec.h
gc /usr/include/gc/gc.h
gc /usr/include/gc/gc_allocator.h
gc /usr/include/gc/gc_backptr.h
gc /usr/include/gc/gc_config_macros.h
gc /usr/include/gc/gc_cpp.h
gc /usr/include/gc/gc_disclaim.h
gc /usr/include/gc/gc_gcj.h
gc /usr/include/gc/gc_inline.h
gc /usr/include/gc/gc_mark.h
gc /usr/include/gc/gc_pthread_redirects.h
gc /usr/include/gc/gc_tiny_fl.h
gc /usr/include/gc/gc_typed.h
gc /usr/include/gc/gc_version.h
gc /usr/include/gc/javaxfc.h
gc /usr/include/gc/leak_detector.h
gc /usr/include/gc_cpp.h
gc /usr/lib/
gc /usr/lib/libcord.so
gc /usr/lib/libcord.so.1
gc /usr/lib/libcord.so.1.5.0
gc /usr/lib/libgc.so
gc /usr/lib/libgc.so.1
gc /usr/lib/libgc.so.1.5.2
gc /usr/lib/libgccpp.so
gc /usr/lib/libgccpp.so.1
gc /usr/lib/libgccpp.so.1.5.0
gc /usr/lib/libgctba.so
gc /usr/lib/libgctba.so.1
gc /usr/lib/libgctba.so.1.5.0
gc /usr/lib/pkgconfig/
gc /usr/lib/pkgconfig/bdw-gc.pc

...
 ╰─ $ crystal env
CRYSTAL_CACHE_DIR=/home/zw963/.cache/crystal
CRYSTAL_PATH=lib:/home/zw963/Crystal/bin/../share/crystal/src
CRYSTAL_VERSION=1.11.1
CRYSTAL_LIBRARY_PATH=/home/zw963/Crystal/bin/../lib/crystal
CRYSTAL_LIBRARY_RPATH=''
CRYSTAL_OPTS='-Dstrict_multi_assign -Dno_number_autocast -Duse_pcre2 -Dpreview_overload_order'

what are the problem you’re trying to solve?

All question solved!

Thank you for your patience.

please note that picking libgc.so also means is going to be dynamically linked to it and not statically linked, even when you passed --static to crystal build.

Since in previous comment you already shared other places where libgc.a, perhaps those fall into the lookup path of the linker and crystal (that adds the -L parameter to the linker).

Be careful when mixing and matching different static versions of libraries, specially some that are built for musl and running or linking with others for glibc, as things will not work.

My personal recommendation: if you want to generate standalone executables relying on musl, build a Crystal compiler with musl and isolate it’s execution environment from your local installation to avoid it picking up your non-musl libraries and environment packages (one of the reasons I run things inside my container image and not build a dockerfile on every project).

Hope that helps.

Cheers.

Thank you for help.

I guess my poor English caused you some misunderstandings from the beginning, my concern is why crystal official package give a libgc.a, but this file not available when i built compiler from my docker, and my static-compiled Crystal compiler (use alpine) seem like never need it when use it built apps.

Of course these question is clear now.

As far as I know, in arch linux (including most distros), the biggest problem with --static is static link to -levent, although this is not impossible, as far as I know, rust can handle it very well. however, this is impossible with current Crystal.

For now, i just use alpine + container solution, but i guess it should work when use with zig gcc, because you proved it before! it just need more time when I am free.

No worries, I just wanted to be sure that all the points were covered.

And don’t worry about your english, I’m not a native english speaker either, so things might get lost in translation on both directions :sweat_smile:

Dealt for a long time with cross-compilation and packaging libraries (when working on RubyInstaller for Windows) so now things like static libraries and linking issues have become trigger words :grimacing:

I might be doing a few follow up on the static compiling subject soon and probably work on better tooling for that.

Good luck on your testing.

Cheers.

1 Like

@luislavena , I try cross compile my first static-linked crystal application use zig cc on my arch laptop successful! i create a PR for fix rake fetch:all, please check.

zig cc logs
 ╰─ $ screenfetch 
                   -`                 
                  .o+`                 zw963@mingfan
                 `ooo/                 OS: Arch Linux 
                `+oooo:                Kernel: x86_64 Linux 6.7.0-arch3-1
               `+oooooo:               Uptime: 1d 47m
               -+oooooo+:              Packages: 1092
             `/:-:++oooo+:             Shell: bash 5.2.21
            `/++++/+++++++:            Resolution: No X Server
           `/++++++++++++++:           DE: GNOME 45.2
          `/+++ooooooooooooo/`         WM: Mutter
         ./ooosssso++osssssso+`        WM Theme: 
        .oossssso-````/ossssss+`       GTK Theme: Adwaita [GTK2/3]
       -osssssso.      :ssssssso.      Icon Theme: Adwaita
      :osssssss/        osssso+++.     Font: Cantarell 8
     /ossssssss/        +ssssooo/-     Disk: 680G / 2.1T (33%)
   `/ossssso+/:-        -:/+osssso+-   CPU: AMD Ryzen 7 7840HS w/ Radeon 780M Graphics @ 16x 5.137GHz
  `+sso+:-`                 `.-/+oso:  GPU: AMD/ATI
 `++:.                           `-/+/ RAM: 23145MiB / 59995MiB
 .`                                 `/


╰─ $ cd ~/Crystal/crystal-china/procodile_cr

╰─ $ shards build --production   --error-trace --static --stats --time  --release --no-debug --cross-compile --target=x86_64-linux-musl --link-flags=-s
Dependencies are satisfied
Building: procodile
Parse:                             00:00:00.000276782 (   1.16MB)
Semantic (top level):              00:00:00.233188923 ( 116.62MB)
Semantic (new):                    00:00:00.001205098 ( 116.62MB)
Semantic (type declarations):      00:00:00.023897147 ( 116.62MB)
Semantic (abstract def check):     00:00:00.007759898 ( 116.62MB)
Semantic (restrictions augmenter): 00:00:00.006996974 ( 116.62MB)
Semantic (ivars initializers):     00:00:00.006312599 ( 132.62MB)
Semantic (cvars initializers):     00:00:00.140108715 ( 148.68MB)
Semantic (main):                   00:00:00.390423472 ( 244.80MB)
Semantic (cleanup):                00:00:00.000399820 ( 244.80MB)
Semantic (recursive struct check): 00:00:00.000769765 ( 244.80MB)
Codegen (crystal):                 00:00:00.315472910 ( 276.80MB)
Codegen (bc+obj):                  00:00:13.865759435 ( 276.80MB)
cc /home/zw963/Crystal/crystal-china/procodile_cr/bin/procodile.o -o /home/zw963/Crystal/crystal-china/procodile_cr/bin/procodile -s -rdynamic -static -L/home/zw963/Crystal/bin/../lib/crystal -lyaml -lpcre2-8 -lgc -lpthread -ldl -levent


╰─ $ cd ~/Crystal/play/magic-haversack/

 ╰─ $ ls tmp/ports/x86_64-linux-musl/*.a
tmp/ports/x86_64-linux-musl/libcrypto.a          tmp/ports/x86_64-linux-musl/libpcre2-8.a
tmp/ports/x86_64-linux-musl/libevent.a           tmp/ports/x86_64-linux-musl/libsqlite3.a
tmp/ports/x86_64-linux-musl/libevent_pthreads.a  tmp/ports/x86_64-linux-musl/libssl.a
tmp/ports/x86_64-linux-musl/libgc.a              tmp/ports/x86_64-linux-musl/libyaml.a
tmp/ports/x86_64-linux-musl/libgmp.a             tmp/ports/x86_64-linux-musl/libz.a
tmp/ports/x86_64-linux-musl/libpcre.a            


 ╰─ $ zig cc -target x86_64-linux-musl /home/zw963/Crystal/crystal-china/procodile_cr/bin/procodile.o -o /home/zw963/Crystal/crystal-china/procodile_cr/bin/procodile -s -rdynamic -static -L./tmp/ports/x86_64-linux-musl -lyaml -lpcre2-8 -lgc -lpthread -ldl -levent -lunwind
 
╰─ $ file /home/zw963/Crystal/crystal-china/procodile_cr/bin/procodile
/home/zw963/Crystal/crystal-china/procodile_cr/bin/procodile: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), static-pie linked, stripped
 
 ╰─ $ /home/zw963/Crystal/crystal-china/procodile_cr/bin/procodile
Welcome to Procodile v1.0.19
For documentation see https://adam.ac/procodile.

The following commands are supported:

  check_concurrency  Check process concurrency
  console            Open a console within the environment
  exec               Execute a command within the environment
  help               Shows this help output
  kill               Forcefully kill all known processes
  log                Open/stream a Procodile log file
  reload             Reload Procodile configuration
  restart            Restart processes
  run                Execute a command within the environment
  start              Starts processes and/or the supervisor
  status             Show the current status of processes
  stop               Stops processes and/or the supervisor

For details for the options available for each command, use the --help option.
For example 'procodile start --help'.

I personally think this is of great improvement for Crystal cross compile. when built a static link binary for other platforms directly on my working laptop (Arch linux) instead docker buildx.

Maybe the crystal compiler can consider output a zig cc version of the link output for link binary for MacOS/ARM64/AMD64_static, considering this technology is already being used on big team as Uber, this choice is reasonable.

I maybe write some bash shell script myself to automate these tasks, but clearly everyone can benefit.

@straight-shoota, should I open a new issue to discuss this?

1 Like

What kind of compiler support would be necessary for using zig linker?
I suppose it’s all just about setting CC and adding some --link-flags, which is essentially runtime configuration.

Maybe the first thing we could use would be a writeup of the process, maybe in form of a guide?

I guess there is nothing special, zig cc supports all cc parameters, the only thing to do is simple, In addition to the original cc link out, add a zig cc link output is enough(very slight changes), e.g.

original out:

cc procodile.o -o procodile -s -rdynamic -static -L/home/zw963/Crystal/bin/../lib/crystal -lyaml -lpcre2-8 -lgc -lpthread -ldl -levent

new zig cc output:

zig cc -target x86_64-linux-musl procodile.o -o procodile -s -rdynamic -static -L./tmp/ports/x86_64-linux-musl -lyaml -lpcre2-8 -lgc -lpthread -ldl -levent -lunwind

the replacement process quite easy use tools like sed:

  1. cc => zig cc -target x86_64-linux-musl
  2. -Llib/crystal => -Lpath_of_static_library
  3. append a -lunwind.

I don’t understand how the original cc command was generated for now, but, anyway, add a zig cc output is trival, for the part of -Lpath_of_static_library, told user modified it manually.

Maybe the first thing we could use would be a writeup of the process, maybe in form of a guide ?

I will. make an initial version at first.

Saw your PR, but sadly you only updated Linux packages (Alpine) and not the macOS/Homebrew counterparts, so now the versions are different.

See how that is done in a previous PR

re: changes to Crystal to support linking against zig cc, are not necessary, you can use the CC wrappers from the magic-haversack repository:

So you only do:

$ CC=x86_64-linux-musl-cc crystal build --cross-compile --target x86-64-linux-musl hello.cr

And the command line it generates should work (perhaps maybe missing the libunwind reference, haven’t checked lately)

The only downside I detected with this approach is that setting up CC before crystal build is that CC is being used by some macros and Link annotations to detect different versions of things:

Which doesn’t work when cross-compiling.

There are a lot of rough edges around this and I will be a bit more cautious recommending this in Crystal guides at this stage.

(This is of course my personal opinion).

Cheers.

Yeah this doesn’t work at all when cross-compiling (regardless of the linker). The auto version detection is based on libraries available on the host system.
See Crystal cannot detect OpenSSL version without pkg-config · Issue #11069 · crystal-lang/crystal · GitHub

I’m aware of that, also that even with pkg-config being present, it will not find the right version as is pointing to current system libraries and not the ones of the target.

I just wanted to list a few other caveats present in the current implementation that could lead to other series of errors on the usage, specially if a guide endorsing cross-compilation is added.

Cheers.

1 Like