静的リンク¶
Crystalは静的リンクをサポートしています。つまり、バイナリを静的ライブラリとリンクして、これらのライブラリを実行時の依存関係として利用可能にする必要がないようにすることができます。これにより、バイナリが大きくなるという犠牲を払って移植性が向上します。
静的リンクは、--static
コンパイラフラグを使用して有効にできます。言語リファレンスの使用方法を参照してください。
--static
が指定されている場合、静的ライブラリのリンクが有効になりますが、排他的ではありません。ライブラリの動的バージョンがコンパイラのライブラリ検索チェーンで静的バージョンよりも上位にある場合(または静的ライブラリが完全に欠落している場合)、生成されたバイナリは完全に静的にリンクされません。静的バイナリをビルドするには、リンクされたライブラリの静的バージョンが利用可能であり、コンパイラがそれらを見つけることができることを確認する必要があります。
コンパイラは、リンクされる静的および動的ライブラリの最初の検索先として、CRYSTAL_LIBRARY_PATH
環境変数を使用します。これを使用して、動的ライブラリとしても利用可能なライブラリの静的バージョンを提供できます。
すべてのライブラリが静的にリンクするのに適しているわけではないため、いくつかの問題が発生する可能性があります。たとえば、openssl
は複雑なことで知られており、glibc
も同様です(完全な静的リンクを参照)。
一部のパッケージマネージャーは、静的ライブラリ用の特定のパッケージを提供しています。たとえば、foo
は動的ライブラリを提供し、foo-static
は静的ライブラリを提供します。静的ライブラリが開発パッケージに含まれている場合もあります。
完全な静的リンク¶
完全に静的にリンクされたプログラムには、動的ライブラリの依存関係がまったくありません。これは、移植可能なプリコンパイルされたバイナリを配信するのに役立ちます。完全に静的にリンクされたCrystalプログラムの顕著な例は、公式配布パッケージのcrystal
およびshards
バイナリです。
プログラムを完全に静的にリンクするには、すべての依存関係がコンパイル時に静的ライブラリとして利用可能である必要があります。これは、特に一般的なlibc
ライブラリでは、場合によっては複雑になることがあります。
Linux¶
glibc
¶
glibc
は、Linuxシステムで最も一般的なlibc
の実装です。残念ながら、静的リンクとの相性が悪く、強く推奨されません。
代わりに、musl-libc
に対する静的リンクが、Linuxでの推奨オプションです。静的にリンクされているため、musl-libc
に対してリンクされたバイナリは、glibcシステムでも実行されます。それがその要点です。
ただし、動的にリンクされたglibc
以外に、他のライブラリを静的にリンクすることはまったく問題ありません。
musl-libc
¶
musl-libc
は、優れた静的リンクサポートを備えた、クリーンで効率的なlibc
の実装です。
静的にリンクされたCrystalプログラムをビルドするための推奨される方法は、musl-libc
に基づいた最小限のLinuxディストリビューションであるAlpine Linuxです。
Alpine Linuxに基づいた公式Dockerイメージは、Docker Hubのcrystallang/crystal
で利用できます。最新リリースにはcrystallang/crystal:latest-alpine
というタグが付けられています。Dockerfileソースは、crystal-lang/distribution-scriptsで利用できます。
これらのDockerイメージには、crystal
コンパイラ、shards
、およびstdlibのすべての依存関係の静的ライブラリがプリインストールされており、glibc
ベースのシステムからでも静的なCrystalバイナリを簡単にビルドできます。Linux用の公式Crystalコンパイラビルドは、これらのイメージを使用して作成されます。
Dockerイメージを使用して、静的にリンクされたHello Worldプログラムをビルドする方法の例を次に示します。
$ echo 'puts "Hello World!"' > hello-world.cr
$ docker run --rm -it -v $(pwd):/workspace -w /workspace crystallang/crystal:latest-alpine \
crystal build hello-world.cr --static
$ ./hello-world
Hello World!
$ ldd hello-world
statically linked
AlpineのパッケージマネージャーAPKも、静的ライブラリをインストールするのに簡単に使用できます。利用可能なパッケージは、pkgs.alpinelinux.orgで見つけることができます。
macOS¶
macOSは、必要なシステムライブラリが静的ライブラリとして利用できないため、完全に静的リンクを公式にサポートしていません。
Windows¶
Windowsは、Win32ライブラリが静的ライブラリとして利用できないため、完全な静的リンクをサポートしていません。
現在、静的リンクはWindowsでのデフォルトのリンクモードであり、動的リンクは-Dpreview_dll
コンパイル時フラグを介してオプトインできます。コンパイラが指定されたディレクトリでライブラリfoo.lib
を検索する場合、静的ライブラリとDLLインポートライブラリを区別するために、静的にリンクする場合は最初にfoo-static.lib
が試行され、動的にリンクする場合は最初にfoo-dynamic.lib
が試行されます。公式のWindowsパッケージは、LLVMを除くすべてのサードパーティ依存関係に対して、静的ライブラリとDLLインポートライブラリの両方が配布されています。
静的リンクは、MicrosoftのCランタイムライブラリの静的バージョン(/MT
)を使用することを意味し、動的リンクは動的バージョン(/MD
)を意味します。追加のCライブラリは、CRTバージョンの混合に関するリンカー警告を回避するために、この点を考慮してビルドする必要があります。静的にリンクしながら動的CRTを使用する方法は現在ありません。
静的依存関係の識別¶
依存関係を静的にリンクする場合は、その静的ライブラリを利用できるようにする必要があります。ほとんどのシステムでは、静的ライブラリがデフォルトでインストールされていないため、明示的にインストールする必要があります。まず、プログラムがどのライブラリにリンクしているかを知る必要があります。
注意
静的ライブラリのファイル拡張子はPOSIXでは.a
、Windowsでは.lib
です。WindowsのDLLインポートライブラリにも.lib
拡張子が付いています。動的ライブラリには、Linuxおよびその他のほとんどのPOSIXプラットフォームでは.so
、macOSでは.dylib
、Windowsでは.dll
が付いています。
ほとんどのPOSIXシステムでは、ツールldd
は実行可能ファイルがリンクしている動的ライブラリを表示します。macOSでの同等のものはotool -L
で、Windowsでの同等のものはdumpbin /dependents
です。
次の例は、Ubuntu 18.04 LTS(crystallang/crystal:0.36.1
dockerイメージ内)でCrystal 0.36.1およびLLVM 10.0を使用してビルドされた単純なHello Worldプログラムのldd
の出力を示しています。結果は、他のシステムやバージョンでは異なります。
$ ldd hello-world_glibc
linux-vdso.so.1 (0x00007ffeaf990000)
libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007fc393624000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fc393286000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fc393067000)
libevent-2.1.so.6 => /usr/lib/x86_64-linux-gnu/libevent-2.1.so.6 (0x00007fc392e16000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fc392c12000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fc3929fa000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc392609000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc393dde000)
これらのライブラリは、Crystalの標準ライブラリの最小限の依存関係です。空のプログラムでも、Crystalランタイムを設定するためにこれらのライブラリが必要です。
これは多くのように見えますが、これらのライブラリのほとんどは実際にはlibcディストリビューションの一部です。
Alpine Linuxでは、muslはより多くのシンボルを1つのバイナリに直接含めるため、リストがはるかに小さくなります。次の例は、Alpine Linux 3.12(crystallang/crystal:0.36.1-alpine
dockerイメージ内)でCrystal 0.36.1およびLLVM 10.0を使用してビルドされた同じプログラムの出力を示しています。
$ ldd hello-world_musl
/lib/ld-musl-x86_64.so.1 (0x7fe14b05b000)
libpcre.so.1 => /usr/lib/libpcre.so.1 (0x7fe14af1d000)
libgc.so.1 => /usr/lib/libgc.so.1 (0x7fe14aead000)
libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x7fe14ae99000)
libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7fe14b05b000)
個々のライブラリは libpcre
、libgc
であり、残りは musl
(libc
) です。Ubuntuの例でも同じライブラリが使用されています。
このプログラムを静的にリンクするためには、これら3つのライブラリの静的バージョンが必要です。
注意
*-alpine
Dockerイメージには、標準ライブラリで使用されるすべてのライブラリの静的バージョンが同梱されています。プログラムが他のライブラリをリンクしない場合、ビルドコマンドに --static
フラグを追加するだけで、完全に静的にリンクできます。
動的ライブラリの検索¶
実行時の動的ライブラリの検索パスは、コンパイル時に環境変数 CRYSTAL_LIBRARY_RPATH
で制御できます。現在、これはLinuxとWindowsでサポートされています。
Linux¶
コンパイル中に CRYSTAL_LIBRARY_RPATH
が定義されている場合、-Wl,rpath
オプションを介してリンカーにそのまま渡されます。正確な動作はリンカーに依存しますが、通常、これはELF実行ファイルの DT_RUNPATH
または DT_RPATH
動的タグエントリに追加されます。特殊な変数 $ORIGIN
/ $LIB
/ $PLATFORM
は、すべてのプラットフォームでサポートされているわけではありません。
Windows¶
標準ライブラリは、試験的な DLL遅延ロードをサポートしており、遅延ロードを使用することでDLLの検索順序を変更する場合があります。
特定のDLLに対して /DELAYLOAD
リンカーフラグが渡された場合、実行ファイルがそのDLLからシンボルを最初にロードするときに、実行ファイルの CRYSTAL_LIBRARY_RPATH
にセミコロンで区切られたパスを宣言された順序で最初に試し、その後 デフォルトの検索順序を使用します。CRYSTAL_LIBRARY_RPATH
内の $ORIGIN
は、実行中の実行ファイル自体のパスに展開されます。たとえば、コンパイル中に CRYSTAL_LIBRARY_RPATH=$ORIGIN\mylibs;C:\bar
で、--link-flags=/DELAYLOAD:calc.dll
が指定され、実行ファイルが C:\foo\test.exe
にある場合、実行ファイルは C:\foo\mylibs\calc.dll
、次に C:\bar\calc.dll
を検索し、その後デフォルトの順序を使用します。
遅延ロードされないDLLは、プログラムの起動時にすぐにロードされ、CRYSTAL_LIBRARY_RPATH
は考慮されません。
デフォルトでは、DLLは遅延ロードされません。ただし、コンパイル時に -Dpreview_win32_delay_load
コンパイル時フラグが指定された場合、コンパイラはインポートライブラリからすべてのDLL依存関係を検出し、コンパイル中にDLLごとに /DELAYLOAD
リンカーフラグを挿入します。