概述
背景
过程宏
Rust的过程宏(Procedural macro)是一个挺好的设计,我们实际工程中的许多地方都会用到它。比如说:
-
使用serde序列化时
#[derive(Serialize)] struct Foo { bar: usize }
-
使用actix进行路径匹配时
#[get("/")] pub fn index() -> String { "Hello".to_owned() }
此外还有很多地方也会用到。由这些例子可以知道,如果我们将Rust作为后端的程序,那么过程宏几乎是必不可少的。
Alpine
相信许多人都是在使用Docker之后才知道这个Linux操作系统的。由于Docker倡导「一个容器执行一个服务」的理念,所以镜像的操作系统应该尽可能地适应相应的服务,而非包罗万象。而Alpine以"Small,Simple,Secure"的3S口号为理念,其镜像仅仅只有5.55MB. 因此,Alpine作为镜像操作系统十分受欢迎。
问题
Rust的过程宏和Alpine都是如此之好,那么,把它们结合一下会怎样呢?
我这里使用的Rust版本为rustc 1.43.0
, Alpine内核版本为5.3.0-51-generic
.
我们首先写个demo:
# Cargo.toml
[package]
name = "demo"
version = "0.1.0"
edition = "2018"
[dependencies]
serde= { version = "1.0", features = ["derive"] }
其主函数只需声明即可:
// src/main.rs
fn main() {}
然后,我们为了把它放在Alpine里,写一个Dockerfile:
FROM rust:alpine AS builder
WORKDIR /app
COPY . .
RUN cargo build --release
FROM alpine
WORKDIR /app
COPY --from=builder /app/target/release/demo ./demo
CMD ["./demo"]
毫无疑问,这是最简单的一个Dockerfile的写法了。
问题一
那么,我们此时运行,会出现什么问题呢?
$ cargo build
error: cannot produce proc-macro for `serde_derive v1.0.106` as the target `x86_64-unknown-linux-musl` does not support these crate types
哦豁,出大问题。
解决一
首先,我们得搞清楚出这问题的原因。
第一,是Rust的过程宏的原理。众所周知,宏就是在编译器编译阶段运行的用户代码,用Rust文档中的话来说,就是一个AST到AST的函数。在Rust的实现中,过程宏的crate的类型必须在Cargo.toml
中声明为proc-macro
:
[lib]
proc-macro = true
而这类的crate,在编译器编译时,是会由rustc动态链接的。
第二,是Alpine的最显著特征。Alpine最显著的特征就是其使用了musl作为标准库libc的实现,而我们熟悉的Ubuntu等符合GNU的操作系统则使用的是GNU-libc. musl-libc的口号就是可嵌入的标准库,其大小特别小。因此,在被Rust链接时,会默认进行静态链接。而将musl-libc静态链接的话,会要求其他所有链接到同一个程序的库都静态链接。
基于以上两点,如果默认静态链接musl-libc, 那么过程宏所在的crate也会被静态链接,也就导致了上述的错误。
解决方法就是关闭rustc在链接时crt-static
的flag, 让我们可以动态链接到musl-libc:
RUSTFLAGS="-C target-feature=-crt-static" cargo build
这样,就可以顺利地进行动态链接从而编译了。
问题二
在设立了-crt-static
的flag之后,我们再一次尝试了编译。这时候,居然又出现了新的错误:
error: linking with `cc` failed: exit code: 1
....
....
....
= note: /usr/lib/gcc/x86_64-alpine-linux-musl/9.2.0/../../../../x86_64-alpine-linux-musl/bin/ld: cannot find Scrt1.o: No such file or directory
/usr/lib/gcc/x86_64-alpine-linux-musl/9.2.0/../../../../x86_64-alpine-linux-musl/bin/ld: cannot find crti.o: No such file or directory
collect2: error: ld returned 1 exit status
....
....
error: could not compile `syn`
解决二
根据错误报告,我们可以发现,这次错误的原因是少了crti.o
这类的,一般静态链接到程序里的bootstrap库。根据经验,这些库是位于操作系统C库的开发包里,对于Alpine来说,就是musl-dev
这个包。因此,我们只需要在编译的环境中安装开发包
apk add --no-cache -U musl-dev
即可。由于这些程序是静态链接的,因此我们在运行环境中不需要安装这样的包。
问题三
看似很顺利地解决了这个问题,我们的demo
程序也顺利地编译出来了。而通用的Docker使用方法都是多阶段构建,也就是一个镜像只用于构建程序,另一个镜像才是真正的跑程序的,这样可以最大程度减少镜像的体积。那么,我们修改了cargo
编译的条件,还是按照最开始的Dockerfile进行构建,结果会是怎样呢?
第一阶段,很顺利地结束了。第二阶段,却又出错了:
$ ./demo
Error loading shared library libgcc_s.so.1: No such file or directory (needed by ./demo)
Error relocating ./demo: _Unwind_Resume: symbol not found
Error relocating ./demo: _Unwind_SetIP: symbol not found
Error relocating ./demo: _Unwind_GetRegionStart: symbol not found
Error relocating ./demo: _Unwind_GetTextRelbase: symbol not found
Error relocating ./demo: _Unwind_RaiseException: symbol not found
Error relocating ./demo: _Unwind_GetIP: symbol not found
Error relocating ./demo: _Unwind_GetIPInfo: symbol not found
Error relocating ./demo: _Unwind_GetDataRelBase: symbol not found
Error relocating ./demo: _Unwind_SetGR: symbol not found
Error relocating ./demo: _Unwind_Backtrace: symbol not found
Error relocating ./demo: _Unwind_GetLanguageSpecificData: symbol not found
看样子是重定位的错误,所以,居然,居然又是链接的错误???
解决三
我们仔细看一下错误信息,知道了错误是出在libgcc_s.so.1
上。libgcc
是干什么的呢?从它的官网上我们可以看到一句话:
The dynamic linker must be able to find
libgcc
.
噢!这一下,全部都水落石出了。我们之前因为把musl-libc改成了动态链接,那么在这里,就自然需要动态链接器来链接了。而按照官网的说法,动态链接器一定需要libgcc
才能运行。但是,因为这是一个全新的Alpine镜像,它居然不自带libgcc
, 所以就出错了。而正常来说如果是静态链接musl,那么也就不需要动态链接器,那么缺少libgcc
也不会报错。
因此,我们只需要在执行前下载libgcc
即可:
apk add --no-cache -U libgcc
Rust 1.44
Rust 1.44解决了musl环境下静态链接到libc的问题。那么,由于写本文的时候stable版本还没出来,我专门下载了rustlang/rust:nightly-alpine
这个镜像来进行实验。
实验结果是,确实不需要设立-crt-static
的flag了,但是,问题二和问题三依然存在。
最终解决方案
根据上面的讨论,我们最终的Dockerfile如下:
对于Rust 1.43及以下版本为:
FROM rust:alpine AS builder
WORKDIR /app
COPY . .
RUN apk add --no-cache -U musl-dev
RUN RUSTFLAGS="-C target-feature=-crt-static" cargo build --release
FROM alpine
WORKDIR /app
COPY --from=builder /app/target/release/demo ./demo
RUN apk add --no-cache -U libgcc
CMD ["./demo"]
对于Rust 1.44及以上版本为:
FROM rust:alpine AS builder
WORKDIR /app
COPY . .
RUN apk add --no-cache -U musl-dev
RUN cargo build --release
FROM alpine
WORKDIR /app
COPY --from=builder /app/target/release/demo ./demo
RUN apk add --no-cache -U libgcc
CMD ["./demo"]
参考
- rust-lang/cargo#7563
- rust-lang/cargo#7564
- rust-lang/rust#34987
- rust-lang/rust#40113
- rust-lang/rust#40174
- withoutboats/fehler#41
- libgcc
最后
以上就是激昂雪糕为你收集整理的解决Alpine上Rust无法使用过程宏的方法背景问题最终解决方案参考的全部内容,希望文章能够帮你解决解决Alpine上Rust无法使用过程宏的方法背景问题最终解决方案参考所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复