20
6

【Golang】Alpine Docker で実行すると error: stdlib.h: No such file or directory. "stdlib.h" が足りないと言われる

Last updated at Posted at 2020-10-23

golang:alpine の Docker イメージで go rungo test を実行すると stdlib.h: No such file or directoryfatal エラーで叱られる。

たまに C がインポートできないと cgo からも叱られる。はたまた、muslgcc がうんたらと叱られる。

「"golang" "alpine" fatal error: stdlib.h: No such file or directory」とググっても日本語で情報が出てこなかったので自分のググラビリティとして。

$ go test ./...
Testing main package
go: downloading ...
...
# runtime/cgo
exec: "gcc": executable file not found in $PATH
  FAIL	github.com/KEINOS/Sample [build failed]
  FAIL	github.com/KEINOS/Sample/hoge [build failed]
現象(gcc足りないエラー)
exec: "gcc": executable file not found in $PATH
現象(cgo使えないエラー)
could not import C (cgo preprocessing failed) (typecheck)
import "C"
現象(Cオブジェクトが足りないエラー)
cannot find crti.o: No such file or directory
現象(gcc使えないエラー)
running gcc failed: exec: "gcc": executable file not found in $PATH
現象(gccがありまてんエラー)
cgo: C compiler "gcc" not found: exec: "gcc": executable file not found in $PATH
  • IMAGE: golang:alpine
    • Go: v1.15.3 linux/amd64
    • OS: Alpine Linux v3.12,x86_64 (Kernel: 4.19.76-linuxkit)

TL; DR (今北産業)

ミニマル・バージョン
# Minimum, at-least-to-install packages to run/build
apk add --no-cache gcc musl-dev
横着バージョン(上記も含まれています)
# Base meta-package to run/build (This includes gcc and musl-dev as well)
apk add --no-cache build-base
Alpine上で開発・ビルド・困憊るなら、とりま入れておけパック(マルチ・ステージビルドにオススメ)
# Better to be installed packages for dev
apk add --no-cache alpine-sdk build-base

🐒  上記の alpine-sdkbuild-base は、Ubuntu などの Debian 系でいう build-essential や、Fedora や CentOS などの RedHat 系でいう Development Tools の Alpine 版みたいなものです。
gcc, musl-dev だけでなく、gitmake と言った cgo(C 言語モジュール)がらみでも良く使われるツールがインストールされるので、Alpine で開発やコンパイルするなら入れておくことをオススメします。

Go のディレクトリについて

Go のディレクトリ

現在の Go(v1.19)はモジュール・モードがデフォルトになっています。そのため、Go 1.15 以降で開発する場合、3 つのディレクトリを意識する必要があります。

  1. Go 本体と関連するディレクトリ(go env GOROOT
  2. ユーザーが指定したパッケージやモジュールの設置先ディレクトリ(GOPATH, go env GOPATH
  3. 個々のプロジェクトディレクトリ(上記以外の任意の作業ディレクトリ)

注意というか、ポイントは 2 番目の GOPATH です。

まず、1 番目の GOROOT のパスには go 本体のバイナリおよび標準パッケージなどが設置されています。

次に 2 番目の GOPATH のパスには、go getgo install が実行された際のパッケージやモジュールがダウンロード、もしくはビルドされたバイナリがインストールされます。

例えば go install <package path>@<version> の場合は、ダウンロードに加え、バイナリをビルドして $GOPATH/bin に設置します。

go install は、以前までの go get -u <package> と同じなのですが、インストール目的の go get -u廃止されました。Go 1.18 以降では、go get -u <package> はプロジェクトの go.mod 内のパッケージのアップデートに特化しました。

そして、GOROOT および GOPATH 以外のディレクトリで go.mod ファイルが存在するディレクトリが作業ディレクトリになります。

TS; DR (Go 初心者が上記に至るまで勉強したコマケーこと)

🐒   先に言ってしまうと、ネットにある Golang 情報が 1.14 以前のものが多く、参考にしていた本も古かったことで、無駄なトライ&エラーをすることになったので、体だけで覚えないように知見を記事に残したという内容です。
新しいバージョンの情報も、古いバージョンから叩き上げで触ってきた人にはわかる差分の内容で、「はじめての Golang」に近い筆者にとっては返って混乱してしまいました。自分の整理も含めてログを残したいと思います。

とある作業マシンで Go 言語(以下 Golang)のソースから拾う・コンパイる必要がありました。しかし Golang がインストールされていません。しかも別途インストールすることが許されず。。。

幸いなことに Docker はインストールされていたので golang:alpine のイメージで試そうと思いました。単純に「コンパイルために重い Ubuntu イメージを使うのもな」というだけで、まさかここまで疲労困憊るとは。とほほ。

$GOPATH/go)にコードを置いちゃダメダメ問題

Go の新旧バージョン違いを理解しないまま、まずはローカルのカレントディレクトリをコンテナの "/go" ディレクトリにマウントしながら起動しました。

$ # Docker でマウントを取ってみる
$ cd /path/to/the/repo
$ docker run --rm -it -v $(pwd):/go golang:alpine /bin/sh
...
/go #

それではテストの実行です。... ... なんか、根本的に動きません。

/go # go test main.go ./...
$GOPATH/go.mod exists but should not

/go # go mod download
$GOPATH/go.mod exists but should not

/go # go mod verify
$GOPATH/go.mod exists but should not

/go # go env
$GOPATH/go.mod exists but should not

$GOPATH/go.mod exists but should not エラー

go.mod が認識されないのかと思いきや、exists とあるので違うようです。

go.mod exists but should not は「go.mod があるけど、あっちゃダメダメ」と言っています。

どうやら上記エラーは GOPATH モード(Go: Dep)とモジュール・モードに関係しているようです。

... ... そう言えば、Go 1.11 から導入されたモジュール・モードが Go 1.16 からデフォルトになったと小耳に挟んでおりました。(手元の教科書には載ってないので Go ワス)

なんか、Go 1.17 では GOPATH は使えなくなるらしい。

確かに環境変数に GO111MODULE がありません。あ、Go のバージョンが 1.15 だらかも新米。

/go # go version
go version go1.15.3 linux/amd64

/go # env
HOSTNAME=xxxxxxxxxxxx
SHLVL=1
HOME=/root
TERM=xterm
PATH=/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
GOPATH=/go
PWD=/go
GOLANG_VERSION=1.15.3

そのため GOPATH モードになっているからなのか、と思い GO111MODULE=on で「常にモジュール対応モードで動作する」ように設定してみたのですが、残念ながらダメでした。

GO111MODULE=onで起動
$ docker run --rm -it -v $(pwd):/go -e GO111MODULE=on golang:alpine /bin/sh
/go #
/go # echo $GO111MODULE
on
/go # go env
$GOPATH/go.mod exists but should not

Go v1.15 なので、そもそもデフォルトで GO111MODULEauto になっているそうです。

つまり「プロジェクトのルート・ディレクトリもしくはカレントに go.mod を見つけたら自動的にモジュール・モードとして動作する」ということです。go.mod は設置しているので「モジュール・モード」は有効になっているはずです。

上記のリンクによれば回避策は2つ。

  1. /go 以外の別のディレクトリにソースコードをマウントして実行する。
  2. export GOPATH= と環境変数の設定値を「空」にセットしてユーザーディレクトリ下にキャッシュさせる。

go.mod exists but should not の原因

原因はソースコードのマウント先でした。つまり、コンテナにソースコードを -v $(pwd):/go でマウントした、/go がマウント先として問題があったのです。

Go の古い教科書を参考にしていたものだから、GOPATH と同じ階層にソースコードをマウントしていたのです。

モジュール・モードの場合、GOPATH で指定されたディレクトリにモジュールをキャッシュして行きます。そのため、そのキャッシュ・ディレクトリに go.mod などの余計なファイルがあったことがエラーの原因でした。

確かに「$GOPATH/go.mod exists but should not」と「$GOPATH/go.mod があるけど禁則事項です」と言ってます。そう言うことかー。

とりあえず、/go ではなく /app にマウントすることにしました。Go と直接関係のないディレクトリならいいので /workspaces でも /tmp/myapp でも構いません。

ローカルのカレントディレクトリを/appにマウントしながら起動
$ cd /path/to/the/repo
$ docker run --rm -it -v $(pwd):/app golang:alpine /bin/sh
/go #
/go # # マウントしたディレクトリに移動
/go # cd /app
/app #
/app # # 今度は go env が表示された。GO111MODULEは空なので auto ∴ モジュールモード
/app # go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/root/.cache/go-build"
GOENV="/root/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build030913204=/tmp/go-build -gno-record-gcc-switches"

"gcc": executable file not found in $PATH エラー

そして気を取り直して、再テストです。先へ進んだものの、動きません。

テストの実行
/app # go test ./...
go: downloading github.com/...
go: downloading github.com/...
...
# runtime/cgo
exec: "gcc": executable file not found in $PATH
FAIL	github.com/KEINOS/Hello-Cobra [build failed]
FAIL	github.com/KEINOS/Hello-Cobra/hoge [build failed]

なんか cgo が怒っています。gcc がない、と。えー cgo 使ってないんだけどなぁ。依存パッケージが使ってるのかしら。

とりあえず gcc をインストールして再度テストして見ます。

gccのインストール
/app # # gcc 入ってない
/app # gcc --version
/bin/sh: gcc: not found
/app #
/app # # gcc 入れる
/app # apk add --no-cache gcc
...
/app # # gcc 入った
/app # gcc --version
gcc (Alpine 9.3.0) 9.3.0
再テスト
/app # go test ./...
# runtime/cgo
_cgo_export.c:3:10: fatal error: stdlib.h: No such file or directory
    3 | #include <stdlib.h>
      |          ^~~~~~~~~~
compilation terminated.
FAIL	github.com/KEINOS/Hello-Cobra [build failed]
FAIL	github.com/KEINOS/Hello-Cobra/hoge [build failed]

やはり、どこかで cgo を使っているらしく、叱られます。今度は stdlib.h が足りないら C です。どうやら依存パッケージが cgo を使っているぽいです。

GitHub にある Golang の Docker のリポジトリに Issue が上がってました。

try adding musl-dev
Not sure why gcc doesnt depend on it.
CGO does not seem to work on golang:1.6-alpine #86 @ GitHub より)

Alpine の cgomusl-dev を必要とするらしいです。そういや、Alpine 系の Issue で「musl-dev 入れろ」ってのは良く見る。

それでは、再度テストをしてみます。... 動きました。:tada:

/app # # musl-dev 入れる
/app # apk add --no-cache musl-dev
...
/app # # テストする
/app # go test ./...
ok      github.com/KEINOS/Sample        0.003s
ok      github.com/KEINOS/Sample/hoge   0.006s
/app # # 🎉 

Yeah, the alpine images are designed to be minimal.
CGO does not seem to work on golang:1.6-alpine #86 @ GitHub より)

「そうさ。Alpine イメージは必要最低限になるように設計されてるのさ」と、ドキュメントを嫁にしろと言ってます。

The main caveat to note is that it does use musl libc instead of glibc and friends, which can lead to unexpected behavior.
...
To minimize image size, additional related tools (such as git, gcc, or bash) are not included in Alpine-based images. Using this image as a base, add the things you need in your own Dockerfile (see the alpine image description for examples of how to install packages if you are unfamiliar).
golang:-alpine | Quick reference | Golang | docker-library @ GitHub より)

「何はともあれ忠告しておくと、Alpine は、glibc とその愉快な仲間たちの代わりに、挙動が読めない musl libc を使っているからね。」と、あります。そして、サイズを極力小さくするため git とか gcc とか bash すら入れていない、と。

そういえば、そうでした。

Alpine Linux のイメージは、(静的staticリンクされた)単体バイナリを動かすのに特化したコンテナ向けなので、Chage & Aska もガッカりするくらい余計なものが入っていないのでした

そのため、動的リンクされたアプリの場合だったり、コンテナ内でコンパイルするには、Dockerfile で必要な物をいちいち入れてあげて使う物だったのを失念しておりました。

「なんか、あれこれ apk パッケージを探してインストールするのも面倒だなぁ」と Ubuntu にあるような「とりあえず入れておけパック」の build-essential みたいなものがないかググったら、「ビルドによく使われるものを集めたパッケージ」と「開発によく使われるものを集めたパッケージ」があるらしい。

ビルドに必要な良くあるものパック
apk add build-base
開発に必要な良くあるものパック
apk add alpine-sdk

確かに、gccmusl-dev を個別に入れなくても build-base でいけた。

WIP です

musl-dev とは筋肉DEVでガッチガチなヤツやねん

Alpine Linux は、他のディストロに慣れた人からは「親の仇」とでも言うくらい(特に musl-dev の件で)ケチョンケチョンに言われるので、何が違うのか調べてみました。同じ軽量 Linux で有名な Arch Linux はディスられていないのが不思議だったのです。

結論から言うと「サポートユーザー対応が大変だから」だと感じました。「Alpine のためだけ」に対応しないといけない事が多いのです。

Alpine Linux は「独特なポリシー設計思想」による俺様実装が多いため、普段使いの OS としては使いにくいったらありゃしないシロモノです。Linux カーネルを自分でビルドするような、基本的な仕組みを理解した人でも、その思想に賛同できなければ、堅牢性を引き換えに面倒が増えただけのディストロだと感じやすいと思います。

逆に言えば「Docker コンテナのベース OS として使う」といった、普段使いの OS 用途でなければ後述する独自仕様により堅牢で軽量なコンテナを作れます。

特に C/C++、Rust や Go と言ったコンパイル型言語で、静的リンク可能な言語で作られたアプリを Docker で提供するのに向いています。つまり、バイナリに依存ライブラリを埋め込んで、単体バイナリとして実行できるアプリ向けの Linux です。

PHP、Python、Ruby、JAVA といったランタイムが別途必要で、実行するのにも依存ライブラリが別途必要とするタイプ(バイナリ単体で実行できないタイプ)のアプリを提供する場合は、Alpine ベースのイメージだとイライラすることが多いかもしれません。

以下は、書き出さないと整理できない自分の備忘録です。それでも「もうちょっと理解したい」と感じた方は、ゴロ寝でもしながら読むと、良く寝れると思いますので、参考情報の 1 つとして引き続きお読みください。

Alpine Linux は登山家的な人向け。素人にはオススメできない。

Alpine Linux は、Arch Linux と並ぶ軽量 Linux として有名です。

とは言え、両者は根本思想が異なり、中身も別物と言っていいくらい違います。

そのため、良い悪いでなく、対比として比べるべきは Arch と Debian 系の Linux だと思います。

Debian ベースの Linux は、ユーザーが選びやすいように全部用意しておき「機能を引き算して使う」のに対し、Arch Linux は最低限のものを提供して、ユーザー自身が使いやすいように「機能を足し算して使う」違いがあると思います。

そのため、Debian ベースの Linux が好まれるのは「全部入っているため、とりあえず、どこでも動くから」だと思います。これに「使い勝手」を加えた Ubuntu などが特に人気なのもそのためです。

逆に「あんなことがしたい」「こんな時には」と色々調べると、大抵 Arch Linux の情報にたどり着きます。これは、至極シンプルな Linux にトッピングして自分好みにするタイプがゆえ、ピンポイントで個別の情報が多いからです。

Ubuntu 系から入ったユーザーが、Debian に移行し、Arch に落ち着くという話しはよく聞きます。どれも同じ「Linux」です。

これは、Windows ユーザでも慣れてくると、まっさらな Windows に自分が必要なアプリとドライバだけにしたくなります。メーカー同梱の Windows には「よかれ」と不要なものが多く入っているからです。

つまり、Linux に慣れてくると「Ubuntu が良い」「Debian が良い」「Arch が良い」というのは、「Fujitsu マシンの Windows がいい」「DELL マシンの Windows マシンがいい」「自作マシンの Windowsがいい」と言った話しと大して変わらないのです。

慣れてきて「引き算」が面倒になった人が、「足し算」式に移行するのが自然な流れではないでしょうか。

対して、Alpine Linux はネットワーク利用を前提とした、セキュリティの堅牢性を重視したディストロです。その副作用として軽量化されたディストロと言えます。

Alpine は「アルパイン」と発音しますが、日本語で「アルペン」(「アルプスの」「高い山脈の」と言った意味)のことです。
話がそれますが、山登りを面白いと感じるのは「苦労した先に美しい光景があるからだ」と思われがちです。しかし、いわゆる登山家や、本格的な登山が趣味の人が「面白い」と感じるのは「攻略」なのです。
つまり、可能な限りイレギュラー(想定外)や無駄な荷物を排除しつつも、安全かつ堅牢に登れるかを念頭に、綿密な計画や準備が「いかに実際にオンスケで進むか」を楽しみ、攻略することを醍醐味とするのです。つまり、十分な準備はするものの、イレギュラーを楽しむ冒険や探検とは目的が異なります。
さて、Alpine Linux の Alpine は "A Linux Powered Integrated Network Engine" の略です。「Linux に統合されたネットワークエンジン」と言うように、ネットワークでの利用を視野に入れているため、「いかに軽量で堅牢なエンジンを作るか」が重要になります。この「ネットワーク」と「セキュリティ」という高い山を軽量かつ堅牢に登れるかの計画が、登山計画のソレにも通じるところから、言い得て妙なネーミングだなと思いました。

とは言え、他のディストロが脆弱というわけではありません。ポリシー(思想)の違いから、手段が違うのです。

そのため、Debian や Arch で出来たことが、Alpine だと別の独特な方法が必要になることが多く、自分好みにカスタムするにもトラブルが多いです。

例えば Alpine Linux は、その独特な思想により、従来より使われてきた標準ライブラリを作り直したり、標準コマンドも独自の実装の仕方をしています。「俺様実装」感満載であるため、従来の Linux の使い勝手とは別の勝手を求められます。そのため、ケチョンケチョンに言われるのです。

カーネルさん出番だす

先述したように、Alpine Linux は、軽量ではあるものの、純粋なカーネル(OS の根幹となるプログラム群)に近くなるまで削ぎ落としたというたぐではありません。

ここで少し「カーネル」について復習してみたいと思います。

「カーネル」とは、「何かに覆われており、直接は触れないが、根幹・本質となるもの」を言います。

日本語で言えば「(牡蠣などの)身」や「種」「核」といったものです。触ろうとしても、殻(シェル)が間にあって、普通には触れない物といったイメージが近いと思います。

そして、OS におけるカーネルとは、「その OS たるものを形作るプログラム群」のことです。つまり、Debian Ubuntu Arch Android Alpine といった Linux OS の共通部分の小さなプログラム群です。

そして、ユーザーは直接触ることができず、シェル(殻)やプログラムを通してでしか触れないものが、カーネルです。Bash や zsh などがシェルと呼ばれるのは、そのためです(ちなみに、Windows でも、PowerShell などの CUI だけでなく「エクスプローラー」も GUI によるシェルです)。

Linux では「ドライバーはカーネルの一部」と聞いたことがあると思いますが、ユーザーに直接ハードウェアを触らせないという意味です。

ゆうてドライバは「入力データをハードウェアの仕様(プロトコル)に変換する」というバイナリ変換的な役割でしかありません。
Linux はハードウェアの入力や出力は「すべてファイルである」と聞いたことがあると思います。これは、コマンドの出力はパイプ渡し(|)やリダイレクション(>>>)で次のコマンドに渡せるのと思想は同じです。
つまり、ユーザーやアプリが、とあるハードウェアにデータを送りたい場合は、そのハードの入力として割り当てられたファイルにデータを書き込みます。例えばプリンタのポートが /dev/lp0 とした場合、イメージとして cat /path/to/data >> /dev/lp0 するようなものと同じなのです。この時、/dev/lp0 を監視して、受け取ったデータをゴニョゴニョして、ハードウェアにバイナリデータを転送する関数やプログラムがカーネルに含まれています。そして、ゴニョゴニョする処理に差し込むのがドライバです。
つまり、このカーネルのファイルに「入力をファイルにも書き込む処理」を差し込んでコンパイルすれば、ロガーやプロトコルアナライザーにもなるのです。この原理は、Alpine であっても Linux であれば変わりはありません。
カーネルをコンパイルしなくても、パッケージマネージャーでドライバを入れたことがあるかもしれません。これは「特定のディレクトリにバイナリ(ドライバ)があったら、ゴニョゴニョ中にパイプ渡しする」という処理が、先のカーネルファイルに記載されているためです。このオーバーヘッドが気になる場合に、カーネルに書き込んでコンパイルするのです。

また、Alpine に限らず、Linux カーネルのプログラム群のうち、ユーザーや開発者にとって重要なのが「標準コマンド」と「標準ライブラリ」です。一般的にユーザーが使うのが「コマンド」で、アプリが使うのが「ライブラリ」です。

「標準」というのは「スタンダード」とも言われ、POSIX や ISO といった規格に準拠しており、Linux がデフォルトで同梱すると定められたものです。これに、ディストロ独自で必ず入れるコマンドやライブラリも「標準」と付いたりします。しかし「そのディストロのユーザーから見ればスタンダード」というだけの違いです。

一般的な Linux の場合、これら標準コマンドやライブラリは個別のファイルとなっています。

そして、ビルドする際に必要なもの(同梱するコマンド、ドライバやライブラリなど)を選別したり、追加してビルドしたものをディストロ(もしくはフレーバー)と呼びます。

Alpine の 1 番の特徴は、これらの標準コマンドと標準ライブラリを、各々 1 つのバイナリにまとめていることです。

標準コマンドが busybox(ビジーボックス)、そして標準ライブラリが musl(マッスル)という「俺様実装」された単体バイナリに集約されています。

この差異が、Alpine が嫌われる一番の原因なのです。

標準コマンド群 busybox

標準コマンドは、例えば、ls echocat といったデフォルトで使えるコマンドです。

先述したように「標準」といっても、ここでは POSIX といった「規格に準拠した動作をするコマンド」を「標準コマンド」と呼びます。昨今、Linux 界隈で Rust 言語が話題になっているのも、この標準に準拠したコマンドを Rust で再実装していくという方向に向いているからです。

ユーザーから見れば、中身(実装言語)が違うだけで、動作は変わらないので気にすることもないのですが。

そして、これらの「標準コマンド」もカーネルの一部であるため、直接は触れず、ターミナルなどから Bash や zsh といったシェル(殻)を通して実行する必要があります。

例えば、以下のように lsecho コマンドは、Alpine Linux では busybox のエイリアスであることが確認できます。

lsやechoコマンドがbusyboxのエイリアスであることを確認する
$ cat /etc/os-release | grep PRETTY_NAME
PRETTY_NAME="Alpine Linux v3.17"

$ which ls echo
/bin/ls
/bin/echo

$ ls -la /bin/ls /bin/echo
lrwxrwxrwx    1 root     root            12 Nov 22 13:06 /bin/echo -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Nov 22 13:06 /bin/ls -> /bin/busybox

$ ls -lah /bin/busybox
-rwxr-xr-x    1 root     root      821.7K Nov 19 10:13 /bin/busybox

このように 200 近い標準コマンドを 1 つにまとめたのが busybox です。

複数コマンドを 1 つにまとめるメリットは、実行ファイルに付くオーバーヘッドが 1 つで済むことです。塵も積もればで、トータルサイズは小さくなります(800KB 程度)。

また、1 回ロードしておけば、ディスクアクセスも減らせます。これが Alpine がネットワーク向けの Linux であるポイントです。

標準ライブラリ群 musl

そして、標準コマンド同様に、標準ライブラリを俺様実装して 1 つにしたものが musl(マッスル)です。他のディストロでは glibc と呼ばれるライブラリ群の代替となるものです。

なお「ライブラリ」ですが、基本的にアプリと同じものです。違いは「呼び出されて使われるもの」がライブラリで、「単体で実行できるもの」がアプリです。そのため、ライブラリは「関数群」のような役割をすることが大半です。main の関数(本体)のないアプリと言ったところでしょうか。

そして「標準ライブラリ」とは、アプリがハードウェアにアクセスしたりできるようなライブラリ(例えばドライバ)や、枯れた関数(良く使われ、叩き上げられた関数)など、OS の共通した動作をアプリに提供したいものをまとめたライブラリのことです。

そして、俺様アプリから標準ライブラリを使いたい時に必要なのが、インターフェースの仕様(入出力の仕様、つまり API)です。

と言うのも、これらの標準ライブラリは、基本的に C 言語のインターフェースを実装しているので、他のプログラム言語からでも、この仕様に準拠していれば使えるようになります。俗に言う「なんとかラッパー」や「なんとかエクステンション」です。

プログラムに慣れてくると、拡張子が .h なファイルを目にしたことがあると思います。

これは C 言語で書かれたプログラム(ここではライブラリ)の定義ファイルで、ヘッダ・ファイルと呼ばれるものです。このファイルには「このプログラムを使うには、●●●の変数の型は●●●、●●●の関数を使うには●●●の型の引数が必要で、戻り値は●●●の型の値」と言った情報が含まれています。

つまり、この定義に準拠してリクエストすれば、そのライブラリの関数などが他の言語からも使えてしまうということです。

逆に言えば Go や Rust で書かれたプログラム(ここではライブラリ)であっても、C 言語の API に準拠して、ヘッダ・ファイルを提供すれば、他の言語からも使ってもらえると言うことです。

もちろん、自分のプログラム言語の型と、ライブラリの(C/C++)言語の型の変換作業が必要になるため、いささかのオーバーヘッドは発生します。しかし、それ以外の実装は全てしてくれていることの方がメリットが大きいのです。

ここで、一旦整理すると「C/C++ のライブラリを他のアプリから使う場合には、ライブラリ本体と、そのヘッダ・ファイルが必要」ということです。

さて、本題の musl ですが、musl 標準ライブラリを俺様アプリから使いたい場合に必要なのが musl-dev です。これをインストールしないと、musl のインターフェースの仕様(API)がわからなくなるため、コンパイルできません。

とあるアプリをライブラリとして自分のプログラム言語で使いたい時に、*-dev パッケージも必要と言われたことがあるかもしれません。例えば、FFmpeg の場合は、ffmpeg-dev も別途必要といったものです。この *-dev にヘッダ・ファイルが含まれているのです。

このように、とあるプログラムが、これら標準ライブラリを利用する場合は、つなぎ・・・となる「ヘッダ・ファイル」という、互換の仕様書みたいなものが必要なのです。

これがアプリをビルド(コンパイル)する際に glibc-dev musl-devsqlite3-dev といった「なんとか-dev」のパッケージを別途インストールする必要がある理由です。

問題は、musl は他のディスロの標準ライブラリを単純

... ばてました。「❤︎」が付いたら、またやる気だします。


参考文献

20
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
6