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:
- What is
libgc.a
used for? i selecttarget=x86_64-linux-musl
when built, but generated bianry giveDefault target: x86_64-alpine-linux-gnu
, Why? - is
libgc.a
only required for static compilation usinglinux-gnu
(instead of linux-musl) target? - How to built
libgc.a
if it is necessory? - myself build file size is 93M, but offical is 80M, both use same Makefile with --release, The size difference is a little big?
- 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!