Продвинутое руководстве по компоновке (advanced linking)
Распространённые ситуации, в которых требовалась компоновка с кодом на Rust, уже были рассмотрены в предыдущих главах книги. Однако для поддержки прозрачного взаимодействия с нативными библиотеками требуется более широкая поддержка разных вариантов компоновки.
Аргументы компоновки (link args)
Есть только один способ тонкой настройки компоновки — атрибут link_args
.
Этот атрибут применяется к блокам extern
, и указывает сырые аргументы, которые
должны быть переданы компоновщику при создании артефакта. Например:
#![feature(link_args)]
#[link_args = "-foo -bar -baz"]
extern {}
# fn main() {}
Обратите внимание, что эта возможность скрыта за feature(link_args)
, так как
это нештатный способ компоновки. В данный момент rustc
вызывает системный
компоновщик (на большинстве систем это gcc
, на Windows — link.exe
),
поэтому передача аргументов командной строки имеет смысл. Но реализация не
всегда будет такой — в будущем rustc
может напрямую использовать LLVM для
связывания с нативными библиотеками, и тогда link_args
станет бессмысленным.
Того же эффекта можно достигнуть с пощощью передачи rustc
аргумента -C link-args
.
Крайне рекомендуется не использовать этот атрибут, и пользоваться вместо него
более точно определённым атрибутом #link(...)
для блоков extern
.
Статическое связывание
Статическое связывание — это процесс создания артефакта, который содержит все
нужные библиотеки, и потому не потребует установленных библиотек на целевой
системе. Библиотеки на Rust по умолчанию связываются статически, поэтому
приложения и библиотеки на Rust можно использовать без установки Rust повсюду.
Напротив, нативные библиотеки (например, libc
и libm
) обычно связываются
динамически, но это можно изменить, и сделать чтобы они также связывались
статически.
Компоновка — это процесс, который реализуется по-разному на разных платформах. На некоторых из них статическое связывание вообще не возможно! Этот раздел предполагает знакомство с процессом компоновки на вашей платформе.
Linux
По умолчанию, программы на Rust для Linux компонуются с системной libc
и ещё
некоторыми библиотеками. Давайте посмотрим на пример на 64-битной машине с
Linux, GCC и glibc
(самой популярной libc
на Linux):
$ cat example.rs
fn main() {}
$ rustc example.rs
$ ldd example
linux-vdso.so.1 => (0x00007ffd565fd000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fa81889c000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fa81867e000)
librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007fa818475000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fa81825f000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa817e9a000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa818cf9000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fa817b93000)
Иногда динамическое связывание на Linux нежелательно: например, если вы хотите использовать возможности из новых библиотек на старых системах или на целевых системах нет таких библиотек.
Статическое связывание возможно с альтернативной libc
, musl
. Вы можете
скомпилировать свою версию Rust, которая будет использовать musl
, и установить
её в отдельную директорию, с помощью инструкции, приведённой ниже:
$ mkdir musldist
$ PREFIX=$(pwd)/musldist
$
$ # Build musl
$ wget http://www.musl-libc.org/releases/musl-1.1.10.tar.gz
[...]
$ tar xf musl-1.1.10.tar.gz
$ cd musl-1.1.10/
musl-1.1.10 $ ./configure --disable-shared --prefix=$PREFIX
[...]
musl-1.1.10 $ make
[...]
musl-1.1.10 $ make install
[...]
musl-1.1.10 $ cd ..
$ du -h musldist/lib/libc.a
2.2M musldist/lib/libc.a
$
$ # Build libunwind.a
$ wget http://llvm.org/releases/3.6.1/llvm-3.6.1.src.tar.xz
$ tar xf llvm-3.6.1.src.tar.xz
$ cd llvm-3.6.1.src/projects/
llvm-3.6.1.src/projects $ svn co http://llvm.org/svn/llvm-project/libcxxabi/trunk/ libcxxabi
llvm-3.6.1.src/projects $ svn co http://llvm.org/svn/llvm-project/libunwind/trunk/ libunwind
llvm-3.6.1.src/projects $ sed -i 's#^\(include_directories\).*$#\0\n\1(../libcxxabi/include)#' libunwind/CMakeLists.txt
llvm-3.6.1.src/projects $ mkdir libunwind/build
llvm-3.6.1.src/projects $ cd libunwind/build
llvm-3.6.1.src/projects/libunwind/build $ cmake -DLLVM_PATH=../../.. -DLIBUNWIND_ENABLE_SHARED=0 ..
llvm-3.6.1.src/projects/libunwind/build $ make
llvm-3.6.1.src/projects/libunwind/build $ cp lib/libunwind.a $PREFIX/lib/
llvm-3.6.1.src/projects/libunwind/build $ cd cd ../../../../
$ du -h musldist/lib/libunwind.a
164K musldist/lib/libunwind.a
$
$ # Build musl-enabled rust
$ git clone https://github.com/rust-lang/rust.git muslrust
$ cd muslrust
muslrust $ ./configure --target=x86_64-unknown-linux-musl --musl-root=$PREFIX --prefix=$PREFIX
muslrust $ make
muslrust $ make install
muslrust $ cd ..
$ du -h musldist/bin/rustc
12K musldist/bin/rustc
Теперь у вас есть сборка Rust с musl
! Поскольку мы установили её в отдельную
корневую директорию, надо удостовериться в том, что система может найти
исполняемые файлы и библиотеки:
$ export PATH=$PREFIX/bin:$PATH
$ export LD_LIBRARY_PATH=$PREFIX/lib:$LD_LIBRARY_PATH
Давайте попробуем!
$ echo 'fn main() { println!("hi!"); panic!("failed"); }' > example.rs
$ rustc --target=x86_64-unknown-linux-musl example.rs
$ ldd example
not a dynamic executable
$ ./example
hi!
thread '<main>' panicked at 'failed', example.rs:1
Успех! Эта программа может быть скопирована на почти любую машину с Linux с той же архитектурой процессора и будет работать без проблем.
cargo build
также принимает опцию --target
, так что вы можете собирать
контейнеры как обычно. Однако, возможно вам придётся пересобрать нативные
библиотеки с musl
, чтобы иметь возможность скомпоноваться с ними.