Moai+Easter Advanced Manual

Moai Manual Advanced Manual Annoucement FAQ
ご案内 Moaiエンジン CustomBoyエンジン HowToコンパイル Moai CGI Developers Raraku言語
HowToコンパイル VC2017を最小限でインストールする方法

はじめに

この記事では、MoaiやRarakuのソースコードから実行バイナリ(exeファイル等)を作る手順について、全くの初心者でもできるように丁寧に説明します。

この記事における基本用語と機能について

この記事では実行バイナリ(Windowsであればexeファイルなど)を作ることをコンパイル(またはビルド)、これを行うためのツールをコンパイラと呼びます。 作成した実行バイナリとそれに付属するその他のファイルは、最終的に所定の位置へコピーする必要がありますが、この過程をインストールと呼びます。

コンパイル作業はすべてPC上で行います。 例えば、Windows向けのバイナリならばWindows PC上で、Linux向けバイナリならばLinux PC上でコンパイルを行います。 また現時点のMoaiやRarakuではAndroid向けバイナリのコンパイルは、Windows PC または Linux PC上でこれを行うことを想定します (このようなコンパイルをクロスコンパイルと呼びます)。

この記事ではWindows環境において、フォルダのことをディレクトリと呼ぶ場合がありますが、両者は同じものです。 おおよそエクスプローラ上から操作する場合はフォルダと呼び、 一方コマンドプロンプトやバッチファイル、パスなどを意識したコンテキストではディレクトリと呼ぶことが多いですが、 使い分けは割と適当でごちゃ混ぜにして使うこともあります。 Linux/Cygwin/BSD/Android/Darwin環境においては、これをすべてディレクトリと呼ぶものとします。

この記事においてグレーのボタンを押すと折りたたみ式のパネルが展開されます(もう一度押すと折りたたまれます)。 ここには主に発展的な説明や参考、補足などが表示されます。

この記事ではWindows/Linux/BSD/Android/Darwin(MacOSX)と、大きく区分して実に五つものOSについて、 コンパイラの入手方法、インストール/設定方法、実行方法に至るまで、かなり丁寧にその手順を書いてあります。

ソースコードの入手がまだの方は「ソースコードの入手方法」を、 それを入手済みの方は「イントロダクション」をまずはご覧下さい。

目次



なぜソースコードからコンパイルするのか?

Moai/Easter/Rarakuは完全なオープンソースです。

全ソースコードを公開しているソフトウェアのことをオープンソースと呼びます。 この言葉は「オープンソースな」といった具合に形容動詞的に使われる場合もあります。 ソースコードはテキストファイルであるため、人間の目でも読むことができます。

また実行バイナリも提供しています。

実行形式はバイナリファイルであるため、人間の目で読むことはできません (少なくともソースコードに比べればその解析は非常に困難です)。 しかし機械(PCやスマホ)上で直接実行させるには通常この形式にする必要があります。

実行バイナリは各OSごとに存在します。

例えばWindows版、Linux版、BSD版、Android版、Darwin版など別個に存在します。 ただしJavaのclassファイルやRarakuのrrkxファイルのように、 すべてのOSで共通に使用できるタイプの実行バイナリもあります。

それらはそれぞれの環境でただちに実行して使うことができます。 多くの方は普通そうされるかと思いますし、そのような方についてはこの記事の内容は不要なものです。

しかしMoaiやRarakuのソースコードにも興味あるのでコンパイルもしてみたいという方もおられることでしょう。 また中にはこのバイナリが安全であるという確証がなければ安心して使えないという用心深い方もおられると思います。 残念ながら「いくらこれは安全です」と作者である我々が言ったところで、ほとんど何の確証にもなりません。

今では我々が開発してきたツールのことをよく知っておられる方も増え、 そのような心配をなさる方をあまり見なくなりましたが、 そうではない方にとってはこれに関する唯一確実な方法はソースコードを見て納得してもらう他ないでしょう。

とはいえ、我々プログラマでさえオープンソースのツールを使うとき、そのコードの隅々まで読んでから使うなど稀なことです。 ましてやプログラマでもない方にこれをせよというのは無茶というものでしょう。

ではどうすればよいでしょうか? 現時点で我々の出せる答えとしては以下のようなものです。
  • ソースコードが公開されている事実だけでよしとして、それ以上は気にしないという考え方

    割とこのような方が多いかもしれません。 この記事の筆者もこのタイプです。

    ソースコードが公開されている時点で、世界中の人間が全てをチェック可能な状況に置かれているということです。 仮に大きな問題があった場合、誰か(詳しい人)がソースコードのどの箇所が問題かをピンポイントで指摘することも一応期待できます。 また逆にそのような報告が挙がっていない場合、それを安全性の担保とみなすこともできます。

  • ソースコードの内容を一応検査してから使う

    あなたにプログラミングに詳しい知り合いがいるなら、このソースコードをざっくりとチェックしてもらうことも可能もしれません。 そんな知り合いはいない方は、プログラミングを扱う掲示板などでこのツールのソースコードのありか(GithubのURL)を記すと何か反応があるかもしれません。 そしてこのツールのソースコードについての意見を聞き、あなたなりの納得を得ることができるかもしれません。

  • 付属するソースコードを自らがコンパイルしてそれを使用する

    現在我々がgithubにおいて配布している実行バイナリは、このソースコードからコンパイルしたものに他なりません。 ですが例えばあなたがそのことを疑って、本当にそうであるという確証が欲しいと思ったとします。 そのためには、あなた自らがソースコードをコンパイルし、出来上がった実行バイナリを使ってもらうしかありません。

この記事でも znk_project で公開されているソースコードについて、 そのファイルがどこにあるのか、何のためのものなのか、中身で何をしているのかなど 場合によってはかなり深いところまで説明する場合もあります (その方がさらに透明性が増すと考えるからです)。 説明が深すぎると思った場合はスルーするなど、必要に応じて情報を取捨選択してください。

目次に戻る

ソースコードの入手方法

現在の最新版はznk_projectという名前のzipファイルとなっており、以下より入手できます。

https://github.com/mr-moai-2016/znk_project/releases/latest

この記事は最新のMoai/Easter/Rarakuのバージョンのソースコード(znk_project)のコンパイル方法についての説明となります。 これより古いバージョンのソースコードのコンパイルについては、この記事で説明している方法では対応できない場合があります。 特にMoai Ver2.3よりビルドシステムが大きく刷新されました。

ソースコードはAssetsにある「Source code (zip)」となっているファイルです。 これをダウンロードし、お使いのPCの適当なディレクトリで解凍します。

補足: Windowsでzipファイルを解凍する方法
最近のWindowsでは右クリックからコンテキストメニューを出し、「すべて展開」を選ぶことでデフォルトでzipファイルを解凍できます。

あるいはzipファイルを解凍するアプリとして 7-Zip の使用をお勧めします。 https://sevenzip.osdn.jp よりダウンロードできます。

7-Zipはコマンドラインからも使用することができ、以下のように実行すると zip を解凍することができます。

"C:\Program Files\7-Zip\7z.exe" x gradle-5.6.4-bin.zip

Close


補足: Linux/BSD/Darwinでzipファイルを解凍する方法
いわゆるUnix系のOSでzipファイルを解凍するには、ターミナルから解凍用のコマンドを実行します。 コマンドとしては unzip コマンドや 7z コマンドなどがあります。 unzip コマンドでは以下のように実行すると zip を解凍することができます。

unzip gradle-5.6.4-bin.zip

もしかすると unzip ではなく unzzip という名前のコマンドがインストールされているかもしれません。 しかしこの unzzip を使うのは避けた方がよいでしょう。 通常のファイルがディレクトリとして展開されるなどおかしな動作をする場合があります。

7z コマンドでは以下のように実行すると zip を解凍することができます。

7z x gradle-5.6.4-bin.zip

7zコマンドがインストールされていない場合、例えばArch Linuxでは以下のようなコマンドでインストールできます。

sudo pacman -S p7zip

Close


補足: Androidでzipファイルを解凍する方法
Androidのバージョンや種類によって、解凍方法は違いますが、 通常、zipファイルをタップすると「zipを解凍する」といった旨(メッセージはこれとは異なるかもしれません)のコンテキストメニューが出るはずです。 それを選ぶことでzipファイルを解凍できます。

Close


目次に戻る

イントロダクション

基本知識


ソースコードからのコンパイルなどやったことがない、あるいは何から手をつければよいかわからないと言う方も多いことでしょう。 C言語/C++(znk_projectが使用しているプログラミング言語の名前)をコンパイルする場合、最低限必要となる基本コマンドは以下の通りです。

  • Cコンパイラ

  • C言語で書かれたソースコードをオブジェクトファイル(object file)に変換します。 ここで言うオブジェクトファイルとは、最終的な実行バイナリ(アプリ)になる前の中間形態のファイルであり、 通常その拡張子は「.o」ないしは「.obj」となります。

    このオブジェクトファイルもまたテキストファイルではなくバイナリファイルとなるため、人間が中身を読むのは困難です。 尚、プログラミングの経験がある方は「オブジェクト指向」という言葉を聞いたことがあるかもしれませんが、 あれは「オブジェクトファイル」とは関係ありません。

  • C++コンパイラ

  • C++言語で書かれたソースコードをオブジェクトファイルに変換します。 ただし znk_project においてはこれは基本的に使われていません。

  • リンカ

  • リンカはオブジェクトファイルを寄せ集めて一つのファイルに結合(link)させます。 これこそが最終的に作りたい実行バイナリに他なりません。

    ただしプロジェクトによってはオブジェクトファイルの数が大量に存在することがあります。 それらを一々個別に指定しては大変ですので、 通常それらを一つに束ねたライブラリ(library)と呼ばれるファイルを作成する方が管理上楽です。 よってリンカが実行バイナリを作る場合、正確にはオブジェクトファイルの他、 いくつかのライブラリを集めて実行バイナリを作ることになります。

    ライブラリには大きく分けて動的ライブラリ(dynamic library)静的ライブラリ(static library)の二通りがあります。 これら二つの違いですが、とりあえず動的ライブラリは最終的な実行バイナリとは別のファイルとしてインストールし、 静的ライブラリは最終的な実行バイナリと(一つのファイルに)一体化させてインストールするものです (本当はまだまだ重要な違いが色々ありますが、まずはこの認識でよいと思います)。 どちらの方法も一長一短あるため、場合によって使い分けます。

    Windowsを使用している方は拡張子がdllというファイルを多分見たことがあるでしょう。 あれが動的ライブラリとなります。 尚、Linux/BSD/Androidの場合は拡張子がso、 Darwinの場合はdylibとなります。

    動的ライブラリそのものもまたリンカを使って作成します。 一方、静的ライブラリはリンカではなく、 次に述べるライブラリアンを使って作成します。

  • ライブラリアン

  • オブジェクトファイルを寄せ集めて静的ライブラリを作成します。

    プログラマ以外の方は静的ライブラリをご覧になる機会は多分あまりないと思います。 拡張子はOSというよりもコンパイラの種類に依存します。 例えば同じWindowsでもVCならば拡張子は lib、MinGW(gcc)ならば拡張子は a となります。 その他、Linux/BSD/Android/Darwinの場合も拡張子は a です。

  • ビルドスクリプト

  • ビルドスクリプトは、上記のコンパイラ、リンカ、ライブラリアン、その他諸々のコマンド群を、 そのプロジェクトの開発者が定義した規則で実行します。

    これがなくても上記のコマンド群はやろうと思えば単独で実行可能ですから、 このようなスクリプトは必須ではありません。 しかし、そのような単独実行を毎回手動でやっていては大変な手間となります。 よってこのビルドスクリプトは事実上の準必須項目と考えてよいでしょう。

    ビルドスクリプトには様々な種類がありますが、 znk_project では Makefile を使用します。 一般に make と呼ばれるコマンドを実行することによって、この Makefile に書かれた規則が実行されることになります。

    ただしこの make コマンドにはいくつか種類があり、 不幸なことにそれらの make コマンドには互換性がなく、 つまりそれぞれの make コマンドに対応した Makefile を別個に用意しなければなりません。

    make コマンドはコンパイラに付属するものを使うこともできますし、別途好きなものをインストールして使うこともできます。 とはいえ、ある程度どの環境ではどの make ファイルを使うものと方針を決めておかないと収拾が付きませんので、 znk_project では次のようにします。

    • VC : VC付属のnmakeコマンドを使うものとする
    • FreeBSD/NetBSD/DragonFly : 標準インストールされているBSD makeコマンドを使うものとする
    • それ以外 : GNU makeコマンドを使うものとする

まずはこれらのコマンドツール一式を取り揃える必要があります。 色々あって大変と思われるかもしれませんが、心配ありません。

Windows/Linux/BSDに限れば準備や手順はそれほど複雑ではなくさほど難しくもないと思います。 ただしAndroidについてはクロスコンパイルを行わなければならないため、その準備や手順ともに複雑で難しいです。 DarwinについてはDarwin上で直接コンパイルする場合はさほど難しくはありませんが、 Linux上でクロスコンパイルする場合はかなり難しいものになります。

以下でこれについても詳しく説明いたします。

Windows版をコンパイルしてみたい方へ


Windows版Moaiそれ自体は(32bit版であれば)Windows98以前でも動作します。 しかしそのWindows版Moaiをコンパイルする環境としては、Windows 2000以降が必要です。

使用するCコンパイラは、特に拘りがなければMinGW32を使うとよいでしょう。 WindowsでMinGWを使ってコンパイルするへお進みください。

MinGW64を使用したいという方も同じくWindowsでMinGWを使ってコンパイルするへお進みください。

MinGWではなくVC(Visual C++)を使いたいという方は、 WindowsでVCを使ってコンパイルするへお進みください。

上記どれでもないCコンパイラ(!?)を使いたいという方は、 目次よりご希望のコンパイラ環境があればそのセクションを参照してください。

Linux版をコンパイルしてみたい方へ


あまりに古い環境でなければ、基本的にはどのLinuxでもLinux版Moaiをコンパイル可能だと思います(因みに筆者はArch Linuxの64bit版を使っております)。

Linux版をコンパイルするへお進みください。

BSD版をコンパイルしてみたい方へ


多分このOSを選択されている方は、その時点でPCについてかなり詳しい方だとは思います。 BSD版についてはこの記事を執筆時点の最新版のものについてテストを行い、 それに基づいたコンパイル方法を紹介しています。 サポートするのはFreeBSD、NetBSD、DragonFly、OpenBSDの四つです。

BSD版をコンパイルするへお進みください。

Android版をコンパイルしてみたい方へ


Android版Moaiそれ自体はAndroid上で動作するものですが、 そのAndroid版MoaiのコンパイルはPC上で行う必要があります。 二つの選択肢があります。Windows上でコンパイルするかLinux上でコンパイルするかです。

Windows上でAndroid版Moaiをコンパイルするには Windows 2000以降が必要です。 ただし apk ファイルまで作成したい場合は Windows XP以降が必要になります。

Linux上でAndroid版Moaiをコンパイルすることも可能です。 あまりに古い環境でなければ、基本的にどのLinuxでもAndroid版Moaiをコンパイル可能だと思います。

ただしAndroid版の場合、開発環境の構築がかなり手間がかかり、また色々とインストールが必要です。 単にコンパイルするだけでもAndroid NDKという開発環境をインストールする必要があります。 Android NDKに関してはダウンロードと解凍するスペースを見込んだとして、ディスク容量として1GB程度の余裕が必要です。

さらにそれをapkファイルにするためには、OpenJDK、Gradle、Android SDKといったツールやライブラリをインストールする必要があります。 これら(Android NDKも含め)トータルとして考えた場合、ディスク容量として4GB程度の余裕は必要かと思います。

実際には、不要なディレクトリを圧縮などして工夫すればこれらを全部あわせても3GB以内に収めることも可能ではあるのですが、 そこに至るまでに解凍用のスペースを使用したり、なんらかの試行錯誤が発生して替わりのバージョンのツールやライブラリの 追加的なダウンロードを見込んだ場合、やはり+1GB程度は余分にあった方がよく、4GB程度の空きは必要になろうという算段です。

Android版をコンパイルするへお進みください。

MacOSX(Darwin)版をコンパイルしてみたい方へ


Ver2.3よりDarwin版のコンパイルもサポートされました。

Darwin版をコンパイルするへお進みください。



目次に戻る

Windows版をコンパイルする(MinGWを使用)

難易度:★

はじめに


MinGWはWindowsにおいては最もメジャーなC言語/C++コンパイラの一つです。 元々は32bit版Windows向けのコンパイラでしたが、現在はオリジナルのプロジェクトは活動を停止し、 MinGW64というプロジェクトがこれを引き継いだ形となっています。 そして紛らわしいのですが、このMinGW64というプロジェクトの中に64bit版Windows向けコンパイラと32bit版Windows向けコンパイラの2つが存在します。 つまり全部で3つあるのですが、この記事ではこれらの区別のため、以下のように呼称して分類します。

  • MinGW32.org

  • 旧来からあるオリジナルのMinGWです。 32bit実行バイナリを作るためのコンパイラ環境です。 この実行バイナリは(32bit/64bit版Windowsを含め)基本的にすべてのWindowsで動作します。

  • MinGW64-32

  • MinGW64が提供するコンパイラのうち、32bit実行バイナリを作るためのコンパイラ環境です。 ただしこれが動作するのは32bit版Windowsの場合は、基本的にはWindows2000 SP4以降に限られます。 64bit版Windowsの場合は、基本的にはどのWindowsでも動作するはずです。

    仮にMinGW64-32で作った32bit実行バイナリをWindows98の環境に持っていって起動させると、 例えば「localtime64関数が存在しない」といった旨のエラーが表示されて実行バイナリの起動に失敗します。 そのため、Windows98でも確実に起動可能にするためには、通常MinGW32.orgが必要となります。

    これはWindows98のmsvcrt.dllではlocaltime64関数が提供されないためです。 一方、Windows2000 SP4以降でのmsvcrt.dllでは、_localtime64、_gmtime64、_time64、_mktime64 などの64ビット互換シンボルが追加されており、 こちらでは問題ないことになります。

    しかし、MinGW64-32のバージョンが新しい場合、新しいWindowsしかサポートしていない可能性もあります (Windowsのバージョンを識別して、MinGW64-32コンパイラ自体が起動できないようになっている場合があります)。 そのため、選択するバージョンには注意する必要があります(この記事ではVer6.4を使います)。

    MinGW64のダウンロードサイト(https://sourceforge.net/projects/mingw-w64/)に 「Toolchains targetting Win32」という項目があり、そちらのリンクを辿っていくと 例えば i686-7.1.0-release-win32-sjlj-rt_v5-rev0.7z といった名前のファイルがダウンロードできます。 これがMinGW64-32です。

    さらにこれを展開しますと(紛らわしいことに単に) mingw32 という名前のディレクトリが出来上がります (この(MinGW64の)ディレクトリmingw32の総サイズは約485MBほどにもなります。一方MinGW32.orgでは総サイズは200MB程度で済みます)。 またこれに含まれるコンパイラ(gcc)自体は32bit版実行バイナリであり、 このコンパイラを使ってビルドした結果出来上がる実行バイナリ(これをターゲットとも呼びます)も32bit版実行バイナリとなります。

  • MinGW64-64

  • MinGW64が提供するコンパイラのうち、64bit実行バイナリを作るためのコンパイラ環境です。 ただしこれが動作するのは64bit版Windowsに限られます。 64bit版Windowsの場合は、基本的にはどのWindowsでも動作するはずです。

    しかし、MinGW64-64のバージョンが新しい場合、新しいWindowsしかサポートしていない可能性もあります (Windowsのバージョンを識別して、MinGW64-64コンパイラ自体が起動できないようになっている場合があります)。 そのため、選択するバージョンには注意する必要があります(この記事ではVer7.1を使います)。

    64bit版Windows上では、状況によっては32bit版実行バイナリよりも64bit版実行バイナリの方が実行速度は速くなる場合もあります。 とはいえ、Moaiを含めほとんどのアプリではその実行速度の差は、通常気になるほどのものではありません。 また後者の方がメモリ上で扱えるデータサイズの最大値が大きくなります。 とはいえ、そこまで大規模なデータを扱うことも通常はないでしょう。 その点が気になる方が64bit版実行バイナリを作成することになります。

    ただし世の中の流れとして、64bit化が進む傾向がありますので、 手に入る情報量としては64bit版の方が多くなっていると考えられます。 そのため、絶対に64bit版Windowsしか使わないのであれば、 いざトラブルが発生した場合、こちらの方が調査がし易いかもしれません。

また単にMinGW32と言った場合はMinGW32.orgとMinGW64-32の総称、 単にMinGW64と言った場合はMinGW64-32とMinGW64-64の総称、 単にMinGWと言った場合はMinGW32.orgとMinGW64の総称(つまり今現在あるMinGWすべて)を意味するものとします。

2038年問題

上でMinGW32.orgは活動を停止していると述べましたが、基本的にこれはどのWindowsでもビルドでき(コンパイラが起動でき)、 ビルドの結果出来上がる実行バイナリも基本的にどのWindowsでも動作するものになります。 そのため、特に問題がないならMinGW32.orgを使う方がより広範に動作するものにはなります。 実際、Moai/Easter/Rarakuをコンパイルする場合、ほとんどの処理においてこれで問題ありません。

しかし、処理の内容によっては古いコンパイラ環境では少し問題が生じる場合もあります。 例えば、libressl(HTTPSの通信処理を提供するライブラリ)では、証明書の有効期間の判定などで時刻の処理が含まれます。 そこで指定された時刻が2038年以降の場合、特に32bitの古いコンパイラ環境では時刻に関する関数群が デフォルトではうまく機能しなくなる可能性もあります(いわゆる2038年問題です)。

MinGW32.orgの場合、デフォルトで使用可能なライブラリmsvcrt.dllの仕様が古い関係 (正確にはこのライブラリで使われるtime_t型のデータサイズが32bitである関係)でこのような問題が生じます。 特に有効期間の判定といった未来の時刻が与えられる可能性のある処理では、 アプリを使用している時刻が2038年よりも前でも問題が発生する可能性はあります(例えば有効期間が2020年から2040年の場合、 現在が2026年であったとしても2040年という時刻を処理することになり、この部分で問題が発生する可能性があります)。

MinGW32.orgでこの問題を回避する場合、使用するライブラリを例えばmsvcrt80.dllに切り替える必要があります。 このようにすることで32bitコンパイラ環境であっても __time64_t 型と呼ばれるtime_t型の代替を使用することができ、 2038年問題を回避できます。 しかしながら、msvcrt80.dllはMicrosoftの使用規約により、我々が作ったプログラムにこれを同梱することができません。 そのためプログラムの使用者はこのmsvcrt80.dllを(プログラムとは別途に)ダウンロードしなければならなくなります。 (しかも一般にMicrosoftが配布するファイルは、そのダウンロード先がコロコロと変わる可能性が高いため、 (適宜検索すればその場所の情報はすぐに出てくるものの) どこにこれが置かれているかの固定的な指示をドキュメントで記述しにくく、 プログラム等で自動的にインストールするような仕組みにもしにくいという問題もあります)。

一方、MinGW64-32の場合、__MINGW_USE_VC2005_COMPAT というマクロを定義してコンパイルすることで、 msvcrt80.dllを使うことなくこの問題を回避することができます。 ただし既に述べたようにMinGW64-32自体がすべてのWindowsをサポートするわけではない点には注意が必要です。

結局これに関してはMinGW32.orgとMinGW64-32のどちらも痛し痒しといったところです。 一方、VC8.0(VS2005)を使った場合、この問題を回避しつつWindows98でも動作する32bitバイナリを作ることができ、 しかも標準ライブラリをスタティックリンクすることでmsvcrt80.dllが不要になります。 そのため、2038年問題を回避しつつWindows98でも動作する32bitバイナリを作りたい場合は、 (プログラムの記述の仕方にもよりますが、一般には)VC8.0(VS2005)が最善と言えるかもしれません。

最後に、MinGW64-64の場合、そもそもこの問題は起こりません(64bit環境の場合、time_tのデータサイズが最初から64bitであるためです)。

ここまでまとめますと、ほとんどのケースの場合(Windows2000 SP4以降の場合)、MinGW64-32を選択するのが最善かと思われます。 絶対に64bit版Windowsしか使わない場合は、MinGW64-64でもよいでしょう。 Windows98でも動作する実行バイナリを作りたい場合はMinGW32.orgを選択してください。 入手およびインストール方法について既に色々なサイトで解説されていますが、 この記事では(Windows版については)MinGWが一番の推しコンパイラですので、 少々気合を入れて解説することに致しましょう。

必要なコマンド群


イントロダクションでは、C/C++での開発において最低限必要となる基本コマンドについて述べました。

ここで今一度、MinGW環境におけるこれらのコマンドについて確認いたしましょう。

ツールの種類 対応コマンド 対象ファイル
Cコンパイラ gcc c から o へ
C++コンパイラ g++ cpp から o へ
リンカ ld o から 実行バイナリ/dllへ
ライブラリアン ar および ranlib o から aへ
ビルドスクリプトmingw32-make ( GNU make )Makefile_mingw.makを使用
標準ライブラリ MinGW WinAPI MinGWインストールディレクトリ


即ち、これらのコマンドが実行できるように必要に応じてインストールすればよいということになります。 尚、MinGW32.orgとMinGW64のいずれの場合でも、上記を使用する形となります (特にビルドスクリプトについてはMinGW64でもmingw32-makeという名前になっていることに注意してください)。

MinGW32.orgのインストール


まずMinGW32.org そのもののインストール方法について述べます。 既にこれをインストールされていたり、MinGW64の方を使う予定の方は読み飛ばして構いません。

この記事執筆現在、MinGW32.orgの公式サイトはhttps://sourceforge.net/projects/mingw となっています。

参考: 公式サイトはwww.mingw.orgではなかったのか?
MinGWの公式サイトは元々はhttp://www.mingw.orgでした。 しかし現在、そのサイトは遺跡のように残っているに過ぎず、 ダウンロードするファイル群もそのすべてが sourceforge の方へ置いてあります。

尚、上記の sourceforge のURLがMinGW32.org公式のものであることの確証を得るのは、現在少し難しくなっています (勿論、昔からsourceforgeのMinGW32.orgのサイトを利用して来た人からすれば、そこが公式であることはわかり切っているわけですが、 今日初めてMinGW32.orgを触れる人にそこが公式であることを(我々の言明以外で)どうやって示せるのかということです)。

上述したMinGWの公式サイト(の名残)に sourceforge についてのリンクが一言あれば話は簡単だったのですが、 それが見当たりません。 そこで今回はカリフォルニア大学アーバイン校(University of California, Irvine)のサイト (URLのドメインはuci.edu)の内容から、間接的にもう一つの証拠を得ることにします。 以下を参照してください。

https://ics.uci.edu/~pattis/common/handouts/mingweclipse/mingw.html

このURLのドメインは「ics.uci.edu」となっていますので、確かにカリフォルニア大学アーバイン校のサイトです (この大学のコンピュータサイエンスの教授 Richard E. Pattis 氏のページとなります)。

勿論、カリフォルニア大学アーバイン校はMinGW32.orgと何の関係もないわけですが、 今回の場合はここを信頼できるサイトということにしましょう。

このサイトの上の方に「Downloading」とあり、すぐ下に「Click MinGW」と書かれたリンクがあります。 このリンクのURLは http://sourceforge.net/projects/mingw/files/ となっており、 https://sourceforge.net/projects/mingw のサブディレクトリです。 よって https://sourceforge.net/projects/mingw 自体もMinGW32.orgの公式サイトであると結論付けます。


ただしこの sourceforge は若干サイトの構造がわかりにくいので、インストーラ(mingw-get)が置かれているURLを一旦以下に示します。

https://sourceforge.net/projects/mingw/files/Installer/mingw-get/

ここにはいくつかのバージョンのインストーラ(mingw-get)がリストされていますが、 この記事執筆時点では mingw-get-0.6.2-beta-20131004-1 が一番新しいようですので、これを選びます。 さらに圧縮形式が zip のもの、すなわち mingw-get-0.6.2-mingw32-beta-20131004-1-bin.zip を選びダウンロードしましょう。

このファイル(mingw-get-0.6.2-mingw32-beta-20131004-1-bin.zip)をダウンロードしましたら、 最終的にMinGW32をインストールしたいフォルダへこのファイルを移動します (あるいは最初からMinGW32をインストールしたいフォルダにこれを直接保存してもよいでしょう)。

例えばフォルダ C:\mingw32_dir 配下へMinGW32をインストールしたい場合、 このzipファイルをC:\mingw32_dir直下へ移動します。

移動先のフォルダでこのzipファイルを解凍し、 展開フォルダの名前を v6.3 に変更しましょう (この名前が必ず v6.3 でなければならないというわけではありませんが、 ここではインストールされるバージョンにちなんだわかりやすく短めの名前としています)。

例えば解凍の結果、フォルダ C:\mingw32_dir\mingw-get-0.6.2-mingw32-beta-20131004-1-bin が出来上がったとします。 このフォルダ mingw-get-0.6.2-mingw32-beta-20131004-1-bin の名前を v6.3 に変更します。

コマンドプロンプトを開き、展開ディレクトリ直下にあるディレクトリ bin へ移動します。 またこのディレクトリ bin の直下に mingw-get.exe が存在することを確認してください。

例えば今回の例の場合、コマンドプロンプトから以下のように実行します。

cd C:\mingw32_dir\v6.3\bin
dir

最後の dir コマンドの実行により、mingw-get.exe が表示されることを確認します。

mingw-get.exe をコマンドラインより以下のように実行します。

mingw-get install base
mingw-get install g++

これで必要なファイルがすべて v6.3\var\cache\mingw-get\packages 内に自動的にダウンロードされます。 またダウンロードが終わった後は、ディレクトリ v6.3 の直下にディレクトリ bin や include や lib などが作られ、 その中に必要なファイルがすべて自動的にインストールされます。 今回の例の場合、この段階でディレクトリ C:\mingw32_dir 全体として 237MB ほどになります。 ただしインストール終了後は C:\mingw32_dir\v6.3\var\cache\mingw-get\packages は不要となりますから、 これをまるごと削除して構いません。 これを削除した場合、最終的には200MBほどになります。

Moai/Raraku/EasterではC++を使用していないため(C言語しか使用していないため)、 今回 g++(C++用のコンパイラ)をインストールする必要はないのですが、 一応、基本的なC++のコードくらいはコンパイルできる状態にしておいた方がよいであろうということで g++ も入れています。

g++ のインストールを省略したい場合は、二行目の「mingw-get install g++」の方は実行する必要はありません。 その場合、最終的には50MBほど節約でき、150MB程度で済むと思います。

これで MinGW32 本体のインストールは終わりです。 今回の例の場合、C:\mingw32_dir\v6.3\bin 内に gcc.exe や mingw32-make.exe というファイルが出来ているはずで、 これらがMinGW32におけるビルドのためのコマンドとなります。

MinGW64-32のインストール


ここでは MinGW64-32 そのもののインストール方法について述べます。 MinGW32.orgやMinGW64-64 を使う予定の方は読み飛ばして構いません。

この記事執筆現在、MinGW64の公式サイトは、一応https://www.mingw-w64.org/ となっています。 しかしこのサイトにおけるリンク「Downloads」では、限定されたバージョンのバイナリしかダウンロードできません (特に古いバージョンをダウンロードできません)。

実は MinGW64 も MinGW32.org と同じく、全てのファイルは sourceforge からダウンロードできるようになっています。 よって実質的な公式サイトは以下であると考えてよいと思います。

https://sourceforge.net/projects/mingw-w64/

尚、上記のURLが、MinGW64公式のものであることは以下のようにして確かめられます。 MinGW64の公式サイトhttps://www.mingw-w64.org/から、 「Downloads」→「Source Code」を選びます。 表示されるページの上の方に「SourceForge」と書かれてありますが、 このURLは https://sourceforge.net/projects/mingw-w64/files/mingw-w64/mingw-w64-release/ となっています。 これは https://sourceforge.net/projects/mingw-w64/ のサブディレクトリです。 従って https://sourceforge.net/projects/mingw-w64/ 自体も当然、MinGW64公式のものであると言えます。

ただしこの sourceforge は若干サイトの構造がわかりにくいです。 今回取得すべきファイルにどうやって至ったのかまで知りたい方は以下をご覧下さい。 手っ取り早く目的のファイルをダウンロードして先に進みたい方は読む必要はありません。

参考: 今回取得すべきファイルにどうやって至ったのか?
まずこのsourceforgeにはMinGW64コンパイラ(gcc)自身のソースコードも置かれていますが、 今回我々が入手したいのはビルド済み実行バイナリとなります (そもそもコンパイラを入手したいのですから、中身がそのコンパイラ自身のソースコードでは困ります)。 緑色の「Download」と書かれたリンクはソースコードですので今回我々が求めるものとは違います。

「Files」と書かれたリンクを選びましょう。 このリンクのURLも以下に示します。

https://sourceforge.net/projects/mingw-w64/files/

次は様々なファイルやディレクトリがリストされたページが表示されるはずです。 ここをルートとして求めるべきファイルを探します。

あくまで目安ですが、右の方に「Downloads/Week」と書かれた数字と小さなグラフがあります。 これはここ一週間でそのリンクがどれだけダウンロードされたかを示します。 よって例えば似たようなファイルがあり、それらの差異がよくわからない場合、 この数字の大きさが参考になるかもしれません。

ただし繰り返しますがこの値はあくまで目安です。 この記事では執筆時点における値を参考としていますが、 あなたが実際にそれらを参照したタイミングによっては、 この値の傾向が変化している可能性もあります。

「Toolchains targetting Win32」とあるリンクを選びます。

今回は答えがわかっているため、最初から「Toolchains targetting Win32」を選びますが、 実際に自力でこういうものを見つけ出す場合、 基本的にはその先にどのようなファイルが置いてあるかは実際に自分で確かめてみるしかありません。

ただし用語から多少候補を絞り込むことはできます。 例えばtoolchainとは通常、ビルドのための実行バイナリ一式を示すことが多い用語です。 他にも「Multilib Toolchains」や「mingw-w64」といったそれらしいリンクがあります。 逆に「Junk」や「Cygwin Snapshots」など明らかに候補としての優先度が低そうな項目があることもわかるでしょう。

また「Toolchains targetting Win64」という項目もありますが、こちらはMinGW64-64となります。 今回の目的はMinGW64-32ですので「Toolchains targetting Win32」の方を選ぶことになります。

あるいは上で述べた「Downloads/Week」の値を参考にすることもできます。 今回の場合「Toolchains targetting Win32」の値が他と比べて大きいため、 ここに求めるべきファイルがある確率は高いと推察できます。

次は「Persoal Builds」と「Automated Builds」の二つの項目があると思いますが、 「Persoal Builds」の方を選びます。 ここまでのリンクのURLも一旦以下に示します。

https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/

これについても「Downloads/Week」の値を参考にしてよいでしょう。

次はおそらくビルドを行った人の名前と思われる項目や、プロジェクトの名前と思われる項目があると思います。 「Downloads/Week」の値を参考に「mingw-builds」とある項目を選びましょう。 ここまでのリンクのURLも一旦以下に示します。

https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/

次はMinGW64のバージョン番号が書かれたリンクがいくつか並んでいます。 今回は「6.4.0」を選びましょう。

これについては、今回我々がビルドで確認したバージョンがたまたまこれだったからという理由です。 うまく動作するなら基本的には他のバージョンでもよいと思います。

「6.4.0」を選んだ場合、「threads-win32」と「threads-posix」の二つの項目があると思いますが、 「threads-win32」の方を選びます。

この二つの違いはあまり気にする必要はないと思いますが、 C++11で導入されたthread関係に関する変なライブラリを使う場合は「threads-posix」の方が必要になります。 ただし「threads-posix」の方が若干遅くなります。 その程度の違いです。 Moai/Raraku/EnterではそもそもC++自体を使っていませんし、 マルチスレッド関係の処理はlibZnkでラップされたものを使用することでポータビリティを担保します。 よって「threads-posix」の方を選ぶ理由はなく、パフォーマンスの観点から「threads-win32」の方を選ぶことになります。

次は「sjlj」と「dwarf」の二つの項目があると思います。 さすがにこれはよく意味がわからないと思います。 どっちが正解というようなこともないのですが、とりあえず「dwarf」の方を選びましょう。

Dwarfとはデバッグ用のファイル形式の名前です。

SJLJ はSetJump/LongJumpの略だそうです。 C言語に詳しい方は setjump/longjump という関数をご存知かと思います。 これを使ってC言語でも擬似的にな例外処理を実現できますが、 「sjlj」はこちらを使用しているコンパイラということになります。

ここは「Downloads/Week」の値を参考にして「dwarf」の方を選びます。

ここまでかなり丁寧にリンクを追っていきましたが、ようやく最後のページに到達しました。 このページのURLを一旦以下に示します。

https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/6.4.0/threads-win32/dwarf/

「i686-6.4.0-release-win32-dwarf-rt_v5-rev0.7z」という名前の7zファイルがあると思います。 このファイルをダウンロードします。

Close


この記事で最終的にダウンロードするファイルのリンクを以下に示します。

MinGW64-32本体 : 42.7 MB : i686-6.4.0-release-win32-dwarf-rt_v5-rev0.7z

MD5 : 78fbf9ebc7b168309cf992be9289de68

このファイルをダウンロードしましたら、最終的にMinGW64-32をインストールしたいフォルダへこのファイルを移動します (あるいは最初からMinGW64-32をインストールしたいフォルダにこれを直接保存してもよいでしょう)。

例えばフォルダ C:\mingw32_dir 配下へMinGW64-32をインストールしたい場合、 この7zファイルをC:\mingw32_dir直下へ移動します。

移動先のフォルダでこの7zファイルを解凍し、 展開フォルダの名前を v6.4 に変更しましょう (この名前が必ず v6.4 でなければならないというわけではありませんが、 ここではインストールされるバージョンにちなんだわかりやすく短めの名前としています)。

7zファイルの解凍については、7-Zipで行いましょう。 7-Zipをまだインストールされていない方は、まずはソースコードの入手方法をご一読の上、 先に 7-Zipのインストールを済ませて下さい。
例えば解凍の結果、フォルダ C:\mingw32_dir\i686-6.4.0-release-win32-dwarf-rt_v5-rev0 が出来上がったとします。 このフォルダ i686-6.4.0-release-win32-dwarf-rt_v5-rev0 の名前を v6.4 に変更します。 またこのv6.4フォルダの中にはさらに mingw32 という名前のフォルダがあると思いますが、 これを dwarf に変更します(これも別にこの名前である必然性はありませんが、 元の7zファイルが何であったのかをわかりやすくするためだけの意味で改名しています)。

最終的に C:\mingw32_dir\v6.4\dwarf\bin が存在するような形にします。
MinGW32.orgの最終バージョンはv6.3ですので、v6.4という時点でMinGW32.orgではないことも判断できます。

コマンドプロンプトを開き、展開ディレクトリ内にあるディレクトリ bin へ移動します。 またこのディレクトリ bin の直下に gcc.exe が存在することを確認してください。

例えば今回の例の場合、コマンドプロンプトから以下のように実行します。

cd C:\mingw32_dir\v6.4\dwarf\bin
dir

最後の dir コマンドの実行により、gcc.exe が表示されることを確認します。

これで MinGW64-32 本体のインストールは終わりです (ダウンロードすべきファイルの選定は難しかったですが、インストール自体はアーカイブを解凍するだけであっけないほど単純です)。 今回の例の場合、この段階でディレクトリ C:\mingw32_dir\v6.4\dwarf 全体として 408MB ほどになります。

Moai/Raraku/EasterではC++を使用していないため(C言語しか使用していないため)、 この記事の目的に限れば g++(C++用のコンパイラ)をインストールする必要はないのですが、 今回はこれも含まれています。

MinGW32の一般的な使い方によるMoaiのビルド


せっかくMinGW32をインストールしたのですから、 MinGWのもっとも基本的かつ一般的な使い方も解説しておきましょう (この解説はMinGW32.orgとMinGW64-32で共通です)。

尚、Moai/Raraku/Easterのビルドはここに書いてある方法でも可能ですが、 もっと簡単な auto_trigger というツールを使った別の方法があります。 手っ取り早くMoaiだけをビルドしたい方はここは読み飛ばし次に進んでも構いません。

MinGW32におけるコマンドは、通常コマンドプロンプト上から実行する必要があります。 ここでも一応テストのため、一旦はコマンドプロンプトを使います。

コマンドプロンプト上でまずは mingw32-make コマンドが使えるかどうかを確認します。 以下のように実行しましょう。

mingw32-make

カレントディレクトリに mingw32-make.exe がない限り、 以下のように表示されると思います。

'mingw32-make.exe' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

どのディレクトリに居てもこのコマンドが実行可能なように、まずは環境変数PATHの設定を行わなければなりません。 環境変数PATHについてよくわからない方は以下の「解説」をご覧下さい。

解説: 環境変数PATHの設定の仕方
環境変数PATHに設定すべき値は、コマンドが存在するディレクトリの候補となるパスをリストとして並べたものになります。 ただし環境変数PATHには最初からこのパスがいくつか設定されているはずです。 それを確認するには以下のように実行します。

echo %PATH%

これを実行した結果、表示される内容は環境によっても変わりますが、例えば以下のようになります。

C:\mingw32_dir> echo %PATH%
C:\Windows\system32;C:\Windows;C:\Program Files (x86)\Git\cmd;C:\Program Files\TortoiseSVN\bin

リストの各要素のパスは、Windowsの場合「;」で区切られます。 上記の場合、指定されているパスは四つあり、 「C:\Windows\system32」、「C:\Windows」、「C:\Program Files (x86)\Git\cmd」、「C:\Program Files\TortoiseSVN\bin」となります。

ユーザがコマンドを入力した場合、コマンドプロンプトの内部ではこの(環境変数PATHの)リストに指定されたパスを前から順番に参照し、 入力されたコマンドの実際の位置を探索します。 しかしながら今回の場合、これらのディレクトリのいずれにも、mingw32-make.exe は(直下に)存在しないため、 「そのようなコマンドはありません」といった旨のエラーメッセージが表示されたわけです。

では、この mingw32-make.exe が存在するディレクトリのパス (今回の例では C:\mingw32_dir\v6.3\bin あるいは C:\mingw32_dir\v6.4\dwarf\bin )を環境変数PATHの値(の一番先頭)に追加しましょう。 そのためには set コマンドを使い、以下のように入力して実行します (このときTabキーを使って補完しながら入力すると便利でしょう)。

MinGW32.orgの場合:
set PATH=C:\mingw32_dir\v6.3\bin;%PATH%

MinGW64-32の場合:
set PATH=C:\mingw32_dir\v6.4\dwarf\bin;%PATH%

最後の「;%PATH%」を忘れずに記述してください。 この部分は既存の環境変数PATHの値に置き換わります。

この「;%PATH%」の指定を忘れると、PATHの値は「C:\mingw32_dir\v6.3\bin」あるいは「C:\mingw32_dir\v6.4\dwarf\bin」だけになってしまいます。 これだけでも一応 mingw32-make.exe は実行できるようになると思いますが、 一方でこれまで実行できていた他のいくつかのコマンドが逆に実行できなくなるかもしれません。

尚、指定に失敗した場合でも一旦コマンドプロンプトのウィンドウを閉じてやり直せば、 環境変数PATHの値は元に戻ります。ですから失敗を恐れる必要は全くありません。 ただし元に戻るということは、コマンドプロンプトのウィンドウを新しく開くたびに 上記の手順をもう一度行わなければならないということでもあります。 毎回これを行うのは面倒なので、実際のMoaiのビルドでは別の方法をとります。 それについては後述します。

念のため環境変数PATHの値をもう一度確認してみましょう。 今回の例の場合、以下のようになるはずです。

MinGW32.orgの場合:
C:\mingw32_dir> echo %PATH%
C:\mingw32_dir\v6.3\bin;C:\Windows\system32;C:\Windows;C:\Program Files (x86)\Git\cmd;C:\Program Files\TortoiseSVN\bin

MinGW64-32の場合:
C:\mingw32_dir> echo %PATH%
C:\mingw32_dir\v6.4\dwarf\bin;C:\Windows\system32;C:\Windows;C:\Program Files (x86)\Git\cmd;C:\Program Files\TortoiseSVN\bin

リストの先頭に、MinGW32のbinのパスが追加されていることを確認してください。

Close


環境変数PATHの設定が完了しましたら、再度以下のように入力し、mingw32-make が無事実行できるかを確認しましょう。

mingw32-make

以下のように表示されれば成功です。 無事 mingw32-make は実行できています。

C:\mingw32_dir> mingw32-make
mingw32-make.exe: *** No targets specified and no makefile found.  Stop.

万一ここでさきほどと同じようなエラー表示されたならば、何らかの理由で環境変数PATHの設定に失敗しているか、 あるいはそもそもmingw32-make.exeが指定したパスに存在していない可能性があります。 念のため、全体の手順をチェックしなおした方がよいでしょう。
Windows Vista以降限定となりますが、where コマンドというものを使うこともできます。 これを以下のように実行すると、mingw32-make コマンドが認識される場合はそのフルパスが表示されます (Linuxなどにおけるwhichコマンドと働きは同じです)。

where mingw32-make

上記の実行結果は、mingw32-make が存在する場合は以下のようになります。

MinGW32.orgの場合:
C:\mingw32_dir>where mingw32-make
C:\mingw32_dir\v6.3\bin\mingw32-make.exe

MinGW64-32の場合:
C:\mingw32_dir>where mingw32-make
C:\mingw32_dir\v6.4\dwarf\bin\mingw32-make.exe

これにより(他の場所にある同名のコマンドではなく)確かに今回インストールしたmingw32-makeが実行されていることが確認できます。

また、念のためmingw32-makeのバージョンも確認しておきます。 以下のように実行しましょう。

mingw32-make --version

上記の結果、次のように表示されることを確認します。

C:\mingw32_dir> mingw32-make --version
GNU Make 3.82.90
...

ここで表示される数字が 3.81 以上であれば問題ありません。

現在出回っている GNU make はほぼ 3.81 以上のものなので、 この値が3.81よりも小さい方がレアケースではあります。 ただ、万一3.81よりも古いバージョンであった場合は、我々の用意した Makefile ではビルドできません。

3.81よりも古いバージョンの GNU make では、パターンルールにディレクトリを含めるような記述がまだサポートされていません。 一方で我々の用意した Makefile ではこの機能を前提として記述しています。 これを使わずに書く(VPATHやvpathと呼ばれるものを使用して書く)ことも可能ではありますが、 そちらの方が記述が煩雑になり留意することも増えます。 しかも上述した通り、現在では3.81よりも古いバージョンに出くわす方がレアケースである上、 仮にmakeコマンドのバージョンが古かったとしても新しいものに置き換えることはさほど難しくはないため、 3.81 以上をボーダーとしています。

ではいよいよMoai/Raraku/Easterのビルドに入りましょう。 znk_project\src配下にこれらのすべてのソースコードが格納されています。 まずはznk_project\srcへ移動します。

znk_project.zip をまだダウンロードされていない方は、まずはソースコードの入手方法をご一読の上、 先に znk_project.zip のダウンロード並びにこのzipファイルの解凍を済ませて下さい。

今、znk_project.zip の展開ディレクトリが C:\znk_project であるとしましょう。 この場合、以下のように実行します。

cd C:\znk_project\src

このディレクトリ src 内に make_world.bat というファイルがあるはずです。 これを実行すると以下のような表示がされると思います。

=== DEBUG_MODE=[optimize] MKF_ID=[mingw] MACHINE=[x86]
0. make
1. make install
2. make clean
3. switch debug-mode
4. switch mkfid (using compiler environment)
5. switch machine (target cpu type)
6. mkfgen_update (update your Makefiles using mkfgen)
7. syncronize mkf_triggers in here from mkfsys installed dir
Q. quit

コマンドラインベースでのメニュー選択UIが表示されますので次の手順を行ってください。
  1. 「3. switch debug-mode」を何回か選んでDEBUG_MODEを「optimize」にします。
  2. 「4. switch mkfid」を何回か選んでMKF_IDを「mingw」にします。
  3. 「5. switch machine」を何回か選んでMACHINEを「x86」にします。
  4. 「1. make install」を選びます。 なんらかの理由でこのインストール処理だけを省略し、つまり純粋にコンパイルだけを行いたい場合は、「1. make install」の替わりに「0. make」を選んでください。
ここで言う「選ぶ」とは、一番先頭にある数字のキーを押すことを意味します。 またはその項目までカーソルキーで移動してEnterキーを押してもよいです。 メニュー項目 3, 4, 5 については「選ぶ」毎にその値が循環的に変化していきます。

MinGWが適切にインストールされているならば、これですべてのコンパイルが自動で行われます。 それぞれのプロジェクトのフォルダ内にout_dirというフォルダが自動的に生成され、その中に目的の実行バイナリが生成されるはずです。

make_world.batは、znk_project/src 配下のすべてのディレクトリ内の Makefile_mingw.mak を一括で実行します。 しかしそれぞれのディレクトリ個別でビルド・インストールすることも可能です。 例えば、znk_project/src/libZnk 配下だけをビルドしたければ一旦そのディレクトリに移動し、 その中で次のように実行します。

mingw32-make -f Makefile_mingw.mak

あるいは、znk_project/src/libZnk 配下だけをビルドとインストールの両方を行いたければ一旦そのディレクトリに移動し、 その中で次のように実行します。

mingw32-make -f Makefile_mingw.mak install


MinGW32 と auto_trigger によるMoaiのビルドおよびインストール


Moai/Raraku/Easterにおいては、コマンドプロンプトを使わずにビルドとインストールを行うこともできます。 しかも大抵の場合、こちらの方が簡単です。

まず環境変数ZNK_MINGW32_DIRにMinGW32がインストールされたトップディレクトリを正しく設定します。 ここでトップディレクトリとは、その配下に bin, include, lib ディレクトリがあるディレクトリのことです。

MinGW32.orgの場合、今回はC:\mingw32_dir\v6.3 配下に bin, include, lib ディレクトリがあります。 すなわちトップディレクトリは C:\mingw32_dir\v6.3 になります。 Windowsの環境変数設定ダイアログより新規で変数名としてZNK_MINGW32_DIR、変数値としてC:\mingw32_dir\v6.3を指定して作成すればよいでしょう。

MinGW64-32の場合、今回はC:\mingw32_dir\v6.4\dwarf 配下に bin, include, lib ディレクトリがあります。 すなわちトップディレクトリは C:\mingw32_dir\v6.4\dwarf になります。 Windowsの環境変数設定ダイアログより新規で変数名としてZNK_MINGW32_DIR、変数値としてC:\mingw32_dir\v6.4\dwarfを指定して作成すればよいでしょう。

Windowsの環境変数設定ダイアログは、「システムのプロパティ」ダイアログの「環境変数」ボタンを押すことで表示することができます。 この「システムのプロパティ」ダイアログの出し方ですが、 通常、「コンピュータ」を右クリックし「プロパティ」→「システムの詳細設定」という順番で選ぶか、 あるいはコントロールパネルの中にそれに該当するアイコンがあると思います。
コマンドプロンプトにおけるsetコマンドとは異なり、 Windowsの環境変数設定ダイアログによる設定は、(再びユーザが明示的に変更しない限りは)永続的に持続します。 そのため一旦設定してしまえば、二回目の再ビルド・再インストールはさらに楽になるというわけです。

尚、環境変数設定ダイアログにおける「システム変数の編集」ダイアログでは 値の修正は勿論、変数名の修正も行うことができます。 これを利用すると一時的にZNK_MINGW32_DIRを無効にしたい場合に便利です。

このような場合、普通に ZNK_MINGW32_DIR 自体を一旦消去してもよいのですが、 そのようにすると、元に戻すのにまた新しく変数名を登録した上で値も全部入力し直さなければなりません。

それよりも変数名 ZNK_MINGW32_DIR を例えば _ZNK_MINGW32_DIR のように少しだけ修正します。 これで ZNK_MINGW32_DIR の設定が一時的に無効となります (「_」という文字自体に無効にするような機能があるわけではなく、 ZNK_MINGW32_DIR という変数が一時的に存在しなくなるため、結果的に無効となるということです)。 元に戻すには変数名 _ZNK_MINGW32_DIR を ZNK_MINGW32_DIR に修正すればよいだけなので、 切り替えが非常に簡単です。

ただしこれもやり過ぎると、環境変数設定ダイアログに登録された変数群自体がゴチャゴチャしてしまいますから ほどほどにしておきましょう。

次にsrcフォルダの直下にあるmake_world.batをダブルクリックなどして実行します。 このとき auto_trigger.exe というツールが内部で起動します。


このauto_trigger.exe自体は我々の方でビルドした実行バイナリです。 この記事の趣旨からすれば、このような最初からビルド済みの実行バイナリを使うのは反則なのですが、 ビルドやインストールを簡単にする基盤ツールとなりますので、これについては最初から用意しています。

しかしこれ自体を一からビルドして使うことも(若干手間は増えてしまいますが)勿論可能です (auto_triggerのソースコードはznk_project/src/auto_trigger内にあります)。 その方法については、参考: auto_trigger.exeそれ自体をビルドしたい場合をお読みください。

以下の画像はauto_trigger.exeが起動した画面です


コマンドラインベースでのメニュー選択UIが表示されますので次の手順を行ってください。
  1. 「3. switch debug-mode」を何回か選んでDEBUG_MODEを「optimize」にします。
  2. 「4. switch mkfid」を何回か選んでMKF_IDを「mingw」にします。
  3. 「5. switch machine」を何回か選んでMACHINEを「x86」にします。
  4. 「1. make install」を選びます。 なんらかの理由でこのインストール処理だけを省略し、つまり純粋にコンパイルだけを行いたい場合は、「1. make install」の替わりに「0. make」を選んでください。
ここで言う「選ぶ」とは、一番先頭にある数字のキーを押すことを意味します。 またはその項目までカーソルキーで移動してEnterキーを押してもよいです。 メニュー項目 3, 4, 5 については「選ぶ」毎にその値が循環的に変化していきます。

MinGWが適切にインストールされているならば、これですべてのコンパイルが自動で行われます。 それぞれのプロジェクトのフォルダ内にout_dirというフォルダが自動的に生成され、その中に目的の実行バイナリが生成されるはずです。

64bit Windows版バイナリを作りたい場合は、上記メニュー項目 5 のMACHINEで「x64」を選びます。 ただし MinGW64 をインストールし、かつ環境変数ZNK_MINGW64_DIRを設定しておくことが前提となります。 MinGW64についてはこの後で詳述します。

デフォルトでは、ディレクトリ src の一つ上の階層にディレクトリmoai-v2.* が生成され、 コンパイルされた実行バイナリや設定ファイルなどの必要なものすべてが、そのディレクトリへとインストールされます。 特に実行バイナリなどプラットフォームによって異なるものはディレクトリ moai-v2.*\bin\$PLATFORM へ格納されます。 ここで$PLATFORMの値は、MinGW32を使った場合 windows-x86 となります。

参考: インストールディレクトリを変更したい場合
環境変数ZNK_INSTALL_DIRでインストールされるディレクトリのパスを明示的に指定することもできます。 その場合、指定したディレクトリへインストールされます。


以上でWindows 32bit版のMoaiのコンパイルとインストールはすべて完了です。 お疲れ様でした。

参考: auto_trigger.exeそれ自体をビルドしたい場合
Moaiのソースコードにおいて、いくつかの基盤ツールだけは最初からビルドされた状態で同梱されています。 例えば auto_trigger.exe(トリガーツール)と raraku.exe (raraku言語のコンパイラ)です。 これらは残りのMoai全体をビルドするための基盤となるツールです。

しかし、それらのツールがまだ存在しないものとして、つまり完全にゼロからビルドしたいという方もおられるかもしれません。 そのような方のためにMoaiのソースコードではその方法も提供されています。 そのためには make_world フォルダ直下にあるbuild_zerobase.batを実行します。

これを実行すると、32bit Windows 版 auto_trigger.exe, raraku.exe だけがまずビルドされます(より正確にはこれらのツールが依存するlibZnk, libRrkもビルドされます)。 一旦 auto_trigger.exe が出来上がれば、そこからはauto_trigger.batを起動することでMoaiの残りすべてのビルドを行うことができます。

参考: なぜいくつかの基盤ツールは最初からビルドされた状態で同梱しているのか
その理由はかなり複雑であり、どのOS向けにビルドするのかによっても異なります。 以下、そのあたりに興味ある方だけお読み下さい。

Windows版Moaiをビルドする場合
このケースでは主に開発者の利便性のため、auto_trigger.exeを最初から同梱しています。 また raraku 言語をコンパイルするために、raraku.exe が使われます。

これらは上述したbuild_zerobase.batを実行することでゼロから生成することもできますので、 Windows版に限るなら、これらをビルドした形で同梱することが必須というわけではありません。 しかし後述するAndroid版の事情により、結局Moaiのソースコードとしてこれらを同梱せざるを得ない結果となります。

尚、64bit Windows版Moaiをビルドする場合でも、32bit Windows版 auto_trigger.exe, raraku.exe があれば十分です。 これらで 64bit Windows版Moaiのビルドにおいて必要な処理をすべて行うことができます。 このとき64bit Windows版 raraku.exe が別途ビルドされますが、こちらは最終的に 64bit Windows版 Moai のインストールディレクトリに同梱され、Moai起動時(ランタイム時)にRaraku Studio等で使用されます。
Linux版Moaiをビルドする場合
このケースでは、そもそも auto_trigger に相当するツールを使いません (コマンドラインからmake_world.shを実行することで全てがビルドされるためです)。 raraku 言語をコンパイルするため、raraku コマンドも使われますが、このコマンドも make_world.sh で最初にビルドされます。
Android版Moaiをビルドする場合
このケースでは、クロスコンパイルという性質上、auto_trigger.exe と raraku.exe が必須となります。

Android版バイナリを(Windows上において)ビルドする場合(このような処理をクロスコンパイルと呼びますが)は、 まず大前提としてAndroid SDK、Android NDK をインストールしていなければなりません。 その上で 32bit Windows版 auto_trigger.exe (トリガーツール)と raraku.exe (raraku言語コンパイラ)がMoaiのビルドにおいては使われます。 しかしながら、もしも仮にこの2つのexeをゼロからビルドするとなると、さらに VC や MinGW をインストールしなければなりません。

Android版バイナリだけを作る開発者にとって、VC や MinGW は本来必要ではないものですから、 これらがなくともAndroid版Moaiのビルドが可能であるのが本来あるべき姿です。 そのため、このような状況にも対応すべく、auto_trigger.exe と raraku.exe を最初からビルドした形で同梱せざるを得ないわけです。

勿論、最初からVCないしはMinGWをインストールしている(あるいはそれを追加でインストールしても構わない)開発者ならば、 build_zerobase.batを実行することで、これらのexeをゼロから生成することもできます。
Close


Close


MinGW64-64のインストール


ここでは MinGW64-64 そのもののインストール方法について述べます。 MinGW32.orgやMinGW64-32 を使う予定の方は読み飛ばして構いません。

この記事執筆現在、MinGW64の公式サイトは、一応https://www.mingw-w64.org/ となっています。 しかしこのサイトにおけるリンク「Downloads」では、限定されたバージョンのバイナリしかダウンロードできません (特に古いバージョンをダウンロードできません)。

実は MinGW64 も MinGW32 と同じく、全てのファイルは sourceforge からダウンロードできるようになっています。 よって実質的な公式サイトは以下であると考えてよいと思います。

https://sourceforge.net/projects/mingw-w64/

尚、上記のURLが、MinGW64公式のものであることは以下のようにして確かめられます。 MinGW64の公式サイトhttps://www.mingw-w64.org/から、 「Downloads」→「Source Code」を選びます。 表示されるページの上の方に「SourceForge」と書かれてありますが、 このURLは https://sourceforge.net/projects/mingw-w64/files/mingw-w64/mingw-w64-release/ となっています。 これは https://sourceforge.net/projects/mingw-w64/ のサブディレクトリです。 従って https://sourceforge.net/projects/mingw-w64/ 自体も当然、MinGW64公式のものであると言えます。

ただしこの sourceforge は若干サイトの構造がわかりにくいです。 今回取得すべきファイルにどうやって至ったのかまで知りたい方は以下をご覧下さい。 手っ取り早く目的のファイルをダウンロードして先に進みたい方は読む必要はありません。

参考: 今回取得すべきファイルにどうやって至ったのか?
まずこのsourceforgeにはMinGW64コンパイラ(gcc)自身のソースコードも置かれていますが、 今回我々が入手したいのはビルド済み実行バイナリとなります (そもそもコンパイラを入手したいのですから、中身がそのコンパイラ自身のソースコードでは困ります)。 緑色の「Download」と書かれたリンクはソースコードですので今回我々が求めるものとは違います。

「Files」と書かれたリンクを選びましょう。 このリンクのURLも以下に示します。

https://sourceforge.net/projects/mingw-w64/files/

次は様々なファイルやディレクトリがリストされたページが表示されるはずです。 ここをルートとして求めるべきファイルを探します。

あくまで目安ですが、右の方に「Downloads/Week」と書かれた数字と小さなグラフがあります。 これはここ一週間でそのリンクがどれだけダウンロードされたかを示します。 よって例えば似たようなファイルがあり、それらの差異がよくわからない場合、 この数字の大きさが参考になるかもしれません。

ただし繰り返しますがこの値はあくまで目安です。 この記事では執筆時点における値を参考としていますが、 あなたが実際にそれらを参照したタイミングによっては、 この値の傾向が変化している可能性もあります。

「Toolchains targetting Win64」とあるリンクを選びます。

今回は答えがわかっているため、最初から「Toolchains targetting Win64」を選びますが、 実際に自力でこういうものを見つけ出す場合、 基本的にはその先にどのようなファイルが置いてあるかは実際に自分で確かめてみるしかありません。

ただし用語から多少候補を絞り込むことはできます。 例えばtoolchainとは通常、ビルドのための実行バイナリ一式を示すことが多い用語です。 他にも「Multilib Toolchains」や「mingw-w64」といったそれらしいリンクがあります。 逆に「Junk」や「Cygwin Snapshots」など明らかに候補としての優先度が低そうな項目があることもわかるでしょう。

また「Toolchains targetting Win32」という項目もありますが、こちらはMinGW64-32となります。 今回の目的はMinGW64-64ですので「Toolchains targetting Win64」の方を選ぶことになります。

あるいは上で述べた「Downloads/Week」の値を参考にすることもできます。 今回の場合「Toolchains targetting Win64」の値が他と比べて大きいため、 ここに求めるべきファイルがある確率は高いと推察できます。

次は「Persoal Builds」と「Automated Builds」の二つの項目があると思いますが、 「Persoal Builds」の方を選びます。 ここまでのリンクのURLも一旦以下に示します。

https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/

これについても「Downloads/Week」の値を参考にしてよいでしょう。

次はおそらくビルドを行った人の名前と思われる項目や、プロジェクトの名前と思われる項目があると思います。 「Downloads/Week」の値を参考に「mingw-builds」とある項目を選びましょう。 ここまでのリンクのURLも一旦以下に示します。

https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/

次はMinGW64のバージョン番号が書かれたリンクがいくつか並んでいます。 今回は「7.1.0」を選びましょう。

これについては、今回我々がビルドで確認したバージョンがたまたまこれだったからという理由です。 基本的には他のバージョンでもよいと思います。

「7.1.0」を選んだ場合、「threads-win32」と「threads-posix」の二つの項目があると思いますが、 「threads-win32」の方を選びます。

この二つの違いはあまり気にする必要はないと思いますが、 C++11で導入されたthread関係に関する変なライブラリを使う場合は「threads-posix」の方が必要になります。 ただし「threads-posix」の方が若干遅くなります。 その程度の違いです。 Moai/Raraku/EnterではそもそもC++自体を使っていませんし、 マルチスレッド関係の処理はlibZnkでラップされたものを使用することでポータビリティを担保します。 よって「threads-posix」の方を選ぶ理由は全くなく、パフォーマンスの観点から「threads-win32」の方を選ぶことになります。

次は「sjlj」と「seh」の二つの項目があると思います。 さすがにこれはよく意味がわからないと思います。 どっちが正解というようなこともないのですが、とりあえず「seh」の方を選びましょう。

SEH(Strucutured Exception Handling)は日本語では構造化例外処理と訳される用語であり、 Windows がシステムとして内蔵する例外処理(C++で言えばtry catch文)のようなものです。 「seh」はこちらを使用しているコンパイラということになります。

SJLJ はSetJump/LongJumpの略だそうです。 C言語に詳しい方は setjump/longjump という関数をご存知かと思います。 これを使ってC言語でも擬似的にな例外処理を実現できますが、 「sjlj」はこちらを使用しているコンパイラということになります。

それで結局どちらを選ぶかですが、「seh」の方がネイティブなWindowsの機能を使っている以上、 パフォーマンスはよいと考えられます。 どのみちMinGW64を使う目的はWindows限定ですので、我々としては「seh」の方でよいと判断しました。 ただし繰り返しになりますがこういうのは状況によりけりで、どっちが正解というようなものではありません。

ここまでかなり丁寧にリンクを追っていきましたが、ようやく最後のページに到達しました。 このページのURLを一旦以下に示します。

https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/7.1.0/threads-win32/seh/

「x86_64-7.1.0-release-win32-rh_v5-rev」で始まる7zファイルが三つほど並んでいると思います。 ここまで来るとどれでも構いませんが、我々が実際に確認したのは「x86_64-7.1.0-release-win32-rh_v5-rev0.7z」です (我々がこれを選んだのは単にファイルサイズが一番小さかったからです(微々たる差ですが))。 よってここから先はこのファイルをダウンロードしたものとして説明していきます。

Close


この記事で最終的にダウンロードするファイルのリンクを以下に示します。

MinGW64-64本体 : 46.7 MB : x86_64-7.1.0-release-win32-seh-rt_v5-rev0.7z

MD5 : c471daf6a774f47d8a514bdf74b175d8

このファイル(x86_64-7.1.0-release-win32-seh-rt_v5-rev0.7z)をダウンロードしましたら、 最終的にMinGW64-64をインストールしたいフォルダへこのファイルを移動します (あるいは最初からMinGW64-64をインストールしたいフォルダにこれを直接保存してもよいでしょう)。

例えばフォルダ C:\mingw64_dir 配下へMinGW64-64をインストールしたい場合、 この7zファイルをC:\mingw64_dir直下へ移動します。

移動先のフォルダでこの7zファイルを解凍し、 展開フォルダの名前を v7.1 に変更しましょう (この名前が必ず v7.1 でなければならないというわけではありませんが、 ここではインストールされるバージョンにちなんだわかりやすく短めの名前としています)。

7zファイルの解凍については、7-Zipで行いましょう。 7-Zipをまだインストールされていない方は、まずはソースコードの入手方法をご一読の上、 先に 7-Zipのインストールを済ませて下さい。
例えば解凍の結果、フォルダ C:\mingw64_dir\x86_64-7.1.0-release-win32-seh-rt_v5-rev0 が出来上がったとします。 このフォルダ x86_64-7.1.0-release-win32-seh-rt_v5-rev0 の名前を v7.1 に変更します。 またこのv7.1フォルダの中にはさらに mingw64 という名前のフォルダがあると思いますが、 これを win_seh に変更します(これも別にこの名前である必然性はありませんが、 元の7zファイルが何であったのかをわかりやすくするためだけの意味で改名しています)。

最終的に C:\mingw64_dir\v7.1\win_seh\bin が存在するような形にします。

コマンドプロンプトを開き、展開ディレクトリ内にあるディレクトリ bin へ移動します。 またこのディレクトリ bin の直下に gcc.exe が存在することを確認してください。

例えば今回の例の場合、コマンドプロンプトから以下のように実行します。

cd C:\mingw64_dir\v7.1\win_seh\bin
dir

最後の dir コマンドの実行により、gcc.exe が表示されることを確認します。

これで MinGW32 本体のインストールは終わりです (ダウンロードすべきファイルの選定は難しかったですが、インストール自体はアーカイブを解凍するだけであっけないほど単純です)。 今回の例の場合、この段階でディレクトリ C:\mingw64_dir\v7.1\win_seh 全体として 435MB ほどになります。

Moai/Raraku/EasterではC++を使用していないため(C言語しか使用していないため)、 この記事の目的に限れば g++(C++用のコンパイラ)をインストールする必要はないのですが、 今回はこれも含まれています。

MinGW64-64の一般的な使い方によるMoaiのビルド


せっかくMinGW64-64をインストールしたのですから、 MinGWのもっとも基本的かつ一般的な使い方も解説しておきましょう。

ただしこれについてはMinGW32のときと基本的に手順は同じです。 そのため、MinGW32で説明したいくつかの解説はここでは省略します。

MinGW64-64におけるコマンドは、通常コマンドプロンプト上から実行する必要があります。 コマンドプロンプト上で mingw32-make コマンドが使えるかどうかを確認します。 (MinGW64-64の場合も、makeコマンドの名前は mingw32-make となっていることに注意しましょう。 mingw64-make という名前ではありません)。 以下のように実行しましょう。

mingw32-make

カレントディレクトリに mingw32-make.exe がない限り、これは実行できないはずです。 環境変数PATHを設定しましょう。

あるいは、環境変数 ZNK_MINGW64_DIR を設定し、znk_project\mkfsys\bat_utils\setenv_mingw.bat を実行しても構いません。 今回の場合、以下のように実行します。

set ZNK_MINGW64_DIR=C:\mingw64_dir\v7.1\win_seh
cd znk_project\mkfsys\bat_utils
setenv_mingw.bat

このsetenv_mingw.bat内でやっていることは結局のところ環境変数PATHの設定です。

環境変数PATHの設定が完了しましたら、再度以下のように入力し、mingw32-make が無事実行できるかを確認しましょう。

mingw32-make

以下のように表示されれば成功です。 無事 mingw32-make は実行できています。

C:\mingw64_dir> mingw32-make
mingw32-make.exe: *** No targets specified and no makefile found.  Stop.



ではMoai/Raraku/Easterのビルドに入りましょう。 znk_project\srcへ移動し、make_world.bat を実行してください。

cd znk_project\src
make_world.bat

これを実行すると以下のような表示がされると思います。

=== DEBUG_MODE=[optimize] MKF_ID=[mingw] MACHINE=[x86]
0. make
1. make install
2. make clean
3. switch debug-mode
4. switch mkfid (using compiler environment)
5. switch machine (target cpu type)
6. mkfgen_update (update your Makefiles using mkfgen)
7. syncronize mkf_triggers in here from mkfsys installed dir
Q. quit

コマンドラインベースでのメニュー選択UIが表示されますので次の手順を行ってください。
  1. 「3. switch debug-mode」を何回か選んでDEBUG_MODEを「optimize」にします。
  2. 「4. switch mkfid」を何回か選んでMKF_IDを「mingw」にします。
  3. 「5. switch machine」を何回か選んでMACHINEを「x64」にします。
  4. 「1. make install」を選びます。 なんらかの理由でこのインストール処理だけを省略し、つまり純粋にコンパイルだけを行いたい場合は、「1. make install」の替わりに「0. make」を選んでください。
ここで言う「選ぶ」とは、一番先頭にある数字のキーを押すことを意味します。 またはその項目までカーソルキーで移動してEnterキーを押してもよいです。 メニュー項目 3, 4, 5 については「選ぶ」毎にその値が循環的に変化していきます。

MinGWが適切にインストールされているならば、これですべてのコンパイルが自動で行われます。 それぞれのプロジェクトのフォルダ内にout_dirというフォルダが自動的に生成され、その中に目的の実行バイナリが生成されるはずです。

make_world.batは、znk_project/src 配下のすべてのディレクトリ内の Makefile_mingw.mak を一括で実行します。 しかしそれぞれのディレクトリ個別でビルド・インストールすることも可能です。 例えば、znk_project/src/libZnk 配下だけをビルドしたければ一旦そのディレクトリに移動し、 その中で次のように実行します。

mingw32-make -f Makefile_mingw.mak

あるいは、znk_project/src/libZnk 配下だけをビルドとインストールの両方を行いたければ一旦そのディレクトリに移動し、 その中で次のように実行します。

mingw32-make -f Makefile_mingw.mak install


MinGW64-64 と auto_trigger によるMoaiのビルドおよびインストール


MinGW64-64においても auto_trigger によるビルドが可能です。 しかも大抵の場合、コマンドプロンプトを使うよりもこちらの方が簡単です。

尚、これについてもMinGW32のときと基本的に手順は同じです。 そのため、MinGW32で説明したいくつかの解説はここでは省略します。

まず環境変数ZNK_MINGW64_DIRにMinGW64-64がインストールされたトップディレクトリを正しく設定します。 ここでトップディレクトリとは、その配下に bin, include, lib ディレクトリがあるディレクトリのことです。

例えば、C:\mingw64_dir\v7.1\win_seh 配下に bin, include, lib ディレクトリがあるならば、トップディレクトリは C:\mingw64_dir\v7.1\win_seh になります。 Windowsの環境変数設定ダイアログより新規で変数名としてZNK_MINGW64_DIR、変数値としてC:\mingw64_dir\v7.1\win_sehを指定して作成すればよいでしょう。

次にsrcフォルダの直下にあるmake_world.batをダブルクリックなどして実行します。 このとき auto_trigger.exe というツールが内部で起動します。


既に述べた通り、auto_trigger.exe自体は我々の方でビルド済みの実行バイナリとなります。 ちなみにこれは32bit版実行バイナリですが、MinGW64-64を使う分には全く影響ありません。

以下の画像はauto_trigger.exeが起動した画面です


コマンドラインベースでのメニュー選択UIが表示されますので次の手順を行ってください。
  1. 「3. switch debug-mode」を何回か選んでDEBUG_MODEを「optimize」にします。
  2. 「4. switch mkfid」を何回か選んでMKF_IDを「mingw」にします。
  3. 「5. switch machine」を何回か選んでMACHINEを「x64」にします。
  4. 「1. make install」を選びます。 なんらかの理由でこのインストール処理だけを省略し、つまり純粋にコンパイルだけを行いたい場合は、「1. make install」の替わりに「0. make」を選んでください。
ここで言う「選ぶ」とは、一番先頭にある数字のキーを押すことを意味します。 またはその項目までカーソルキーで移動してEnterキーを押してもよいです。 メニュー項目 3, 4, 5 については「選ぶ」毎にその値が循環的に変化していきます。

MinGWが適切にインストールされているならば、これですべてのコンパイルが自動で行われます。 それぞれのプロジェクトのフォルダ内にout_dirというフォルダが自動的に生成され、その中に目的の実行バイナリが生成されるはずです。

デフォルトでは、ディレクトリ src の一つ上の階層にディレクトリmoai-v2.* が生成され、 コンパイルされた実行バイナリや設定ファイルなどの必要なものすべてが、そのディレクトリへとインストールされます。 特に実行バイナリなどプラットフォームによって異なるものはディレクトリ moai-v2.*\bin\$PLATFORM へ格納されます。 ここで$PLATFORMの値は、MinGW64-64を使った場合 windows-x64 となります。

参考: インストールディレクトリを変更したい場合
環境変数ZNK_INSTALL_DIRでインストールされるディレクトリのパスを明示的に指定することもできます。 その場合、指定したディレクトリへインストールされます。


以上でWindows 64bit版のMoaiのコンパイルとインストールはすべて完了です。 お疲れ様でした。

目次に戻る

Windows版をコンパイルする(VCを使用)

難易度:★

必要なコマンド群


以下ではVC(Visual C++)でのコンパイル方法について説明します。 VCは既にお使いのPCにインストールされているものと仮定して話を進めます。

Moaiをコンパイルするには、使用するWindowsは2000以降でなければなりません。 Moaiそれ自体はWindows98で動作するようなバイナリにコンパイルすることは可能ですが、そのコンパイルを行う側のWindowsはWindows2000以降でなければならないということです。 Visual Studio 2005以前であればWindows98でも動作するようですがここではそれは関係なく、Moaiのソースコードに同梱してあるビルドスクリプト(batファイルやMakefileのことですが)が Windows 2000以降で動作する文法で記述してあるため、このような制限を設けています。 このようなビルドスクリプトに一切頼らないのであれば、原理的にはWindows98上でコンパイルすることも可能かもしれませんが、 この記事ではそこまではサポートしません。

Moaiのコンパイルはすべてコマンドラインベースで行われます。 IDEを起動する必要はありません(make_world.bat ファイルをダブルクリックして実行する形となります)。

イントロダクションでは、C/C++での開発において最低限必要となる基本コマンドについて述べました。

ここで今一度、VC環境におけるこれらのコマンドについて確認いたしましょう。

ツールの種類 対応コマンド 対象ファイル
Cコンパイラ cl c から obj へ
C++コンパイラ cl cpp から obj へ
リンカ link obj から 実行バイナリ/dllへ
ライブラリアン lib obj から libへ
ビルドスクリプトnmake Makefile_vc.makを使用
標準ライブラリ VC Cライブラリ+Windows SDKVCおよびWindows SDKインストールディレクトリ


これらのうちコマンドとCライブラリについてはどのバージョンのVCでも確実に付属しています。 Windows SDK については別途ダウンロードして用意しなければならない場合もあります。

環境変数INCLUDEとLIBとPATHの指定

VCは環境変数INCLUDE, LIB, PATHに指定された情報から、必要なファイルのすべての位置を検索します。 よって究極的にはこれらの環境変数が正しく設定されていさえすればVCによるビルドが可能となります。 このことは今後のいかなるバージョンのVCでも不変かと思われます。

これらの環境変数の値は、いくつかの検索用パスの候補が「;」文字区切りで指定された文字列です。

INCLUDEではヘッダファイルが置かれた場所を検索しますし、LIBではライブラリファイルの場所を検索します。 またPATHでは、 cl.exe, link.exe, nmake.exe などの基本コマンドの場所を検索します。

しかしながらこれらを手動で設定するのはかなり大変なので(特に環境変数PATHが)、 通常は我々が用意した znk_project\mkfsys\bat_utils\setenv_vc.bat を使う方がよいでしょう。 ただしこの記事で説明する方法ではこのsetenv_vc.batを直接呼び出すのではなく auto_triggerというツールを使って間接的に呼び出します。

VCのバージョンによっては、cl.exe コマンドが依存するDLLなどが存在するパスも環境変数PATHに追加しておく必要があるかもしれません。 この辺りが非常に厄介です。 ここで明確に説明できない理由も、未知のVCのバージョンではその設定内容が断定できないためです。

万一 znk_project\mkfsys\bat_utils\setenv_vc.bat が正常に動作しない場合、 これに依存する auto_trigger も使えないことになります。

こうなると最後の手段として、すべて自力で上記の環境変数の設定を行うしかありません。 これらをすべて自力で適切に設定した後、コマンドプロンプト上で make_worldディレクトリへ移動し、nmake -f Makefile_vc.mak install を実行します。

Moaiのコンパイルおよびインストール


Moai Ver2.3 のソースコードから、VCでのコンパイル方法が大きく変更になりました。 以下の3つの環境変数をあらかじめ設定しておく必要があります。
  • ZNK_VC_DIR

  • VC本体のトップディレクトリの位置になります。
    指定する値は、「VC」という名前のディレクトリが現れるまでのパスです(「VC」までは含めて指定します)。 あるいは bin, include, lib が直下に現れるまでのパスを明示的に指定していただいても構いません。 どちらでもよいということです。

    「VC」という名前のディレクトリは、VS2003では「Vc7」、VC6.0では「VC98」という名前になっていると思います。 (あまりいないとは思いますが)これらを使っている方は参考にしてください。

    またVS2017以降は「VC」ディレクトリの直下にbin, include, libは配置されておらず、 「VC\Tools\MSVC\バージョン番号」といったディレクトリの直下に位置が変更されています。 この場合でも「VC」ディレクトリが現れるまでのパスを指定してもよいですし、 「VC\Tools\MSVC\バージョン番号」までのパスを指定してもよいです。 どちらでも構いません。

  • ZNK_VC_WINSDK_DIR

  • Windows SDKのトップディレクトリの位置になります。
    指定する値は、「Program Files (x86)」に相当するディレクトリが現れるまでのパスです。 あるいは bin, include, lib が直下に現れるまでのパスを明示的に指定していただいても構いません。 どちらでもよいということです。

  • ZNK_VC_UCRT_DIR

  • Windows Universal CRT SDKのトップディレクトリの位置になります。
    これに関してはVS2015以降をお使いの方が対象になります (Universal CRTとはVS2015以降に新しく導入されたC言語標準ライブラリです)。 また、Windows SDK 10を使っている場合は、通常そのトップディレクトリと同じになるはずです。
    指定する値は、「Program Files (x86)」に相当するディレクトリが現れるまでのパスです。 あるいは bin, include, lib が直下に現れるまでのパスを明示的に指定していただいても構いません。 どちらでもよいということです。

これらの環境変数は、Windowsの環境変数設定ダイアログより設定しても構いませんが、 この記事では以下のようにバッチファイルを作っておく方法をお勧めします。

Moaiのソースコード内の mkfsys\bat_utils ディレクトリへ移動します。 detect_vc_config_sample.bat というテンプレートファイルがありますので、これを同ディレクトリ内でコピーして detect_vc_config.bat を作ります。 これをテキストエディタで編集します。 これの中身はたとえば以下のような感じです。

修正例(VS2017の場合なら典型的にはこのような感じかと思います):
REM VC Directory
    set        ZNK_VC_DIR=C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC
REM set        ZNK_VC_DIR=D:\Program Files (x86)\Microsoft Visual Studio 14.0\VC
REM set        ZNK_VC_DIR=C:\MyVC\VC


REM Windows SDK
    set ZNK_VC_WINSDK_DIR=C:\Program Files (x86)
REM set ZNK_VC_WINSDK_DIR=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A
REM set ZNK_VC_WINSDK_DIR=D:\Program Files (x86)
REM set ZNK_VC_WINSDK_DIR=D:\Program Files (x86)\Windows Kits\8.1


REM Windows Universal CRT SDK
    set   ZNK_VC_UCRT_DIR=C:\Program Files (x86)
REM set   ZNK_VC_UCRT_DIR=D:\Program Files (x86)
REM set   ZNK_VC_UCRT_DIR=D:\Program Files (x86)\Windows Kits\10

バッチファイルの文法について簡単に説明します。 といってもdetect_vc_config.batでは抑えておくポイントは2点しかありません。
  1. setコマンド

  2. 後ろに続く代入により、環境変数を設定してます(=の左側が環境変数名、右側がその値です)。

    setの前後に(見なすさなどのために)半角スペースをいくつか置くのは構いません。 ただしその他の箇所には(値そのものにそれが含まれている場合を除き)不要な半角スペースなどを入れてはいけません。

  3. 行頭のREM

  4. その行がコメントであることを意味し、その行の記述はまるごと無視されます。 コメントはその箇所の意味をユーザに示すのに(あるいは自分用の備忘録として)利用できます。 また、いくつかの候補がある場合にその他を一時的に無効にするのにも利用できます。

    REMの直後に何か文字列を置く場合は、その間に最低ひとつ以上半角スペースを挟む必要があります。

環境変数の設定が終わりましたら、srcフォルダの直下にあるmake_world.batを実行します。 このとき auto_trigger.exe というツールが起動します。


このauto_trigger.exe自体は我々の方でビルドした実行バイナリです。 この記事の趣旨からすれば、このような最初からビルド済みの実行バイナリを使うのは反則なのですが、 ビルドやインストールを簡単にする基盤ツールとなりますので、これについては最初から用意しています。

しかしこれ自体を一からビルドして使うことも(若干手間は増えてしまいますが)勿論可能です (auto_triggerのソースコードはznk_project/src/auto_trigger内にあります)。 その方法については、参考: auto_trigger.exeそれ自体をビルドしたい場合をお読みください。

以下の画像はauto_trigger.exeが起動した画面です


コマンドラインベースでのメニュー選択UIが表示されますので次の手順を行ってください。
  1. 「3. switch debug-mode」を何回か選んでDEBUG_MODEを「optimize」にします。
  2. 「4. switch mkfid」を何回か選んでMKF_IDを「vc」にします。
  3. 「5. switch machine」を何回か選んでMACHINEを「x86」あるいは「x64」にします。
  4. 「1. make install」を選びます。 なんらかの理由でこのインストール処理だけを省略し、つまり純粋にコンパイルだけを行いたい場合は、「1. make install」の替わりに「0. make」を選んでください。

ここで言う「選ぶ」とは、一番先頭にある数字のキーを押すことを意味します。 またはその項目までカーソルキーで移動してEnterキーを押してもよいです。 メニュー項目 3, 4, 5 については「選ぶ」毎にその値が循環的に変化していきます。

VCが適切にインストールされているならば、これですべてのコンパイルが自動で行われます。 それぞれのプロジェクトのフォルダ内にout_dirというフォルダが自動的に生成され、 その中に目的の実行バイナリが生成されるはずです。

尚、上記メニュー項目 5 のMACHINEで「x64」を選んだ場合は64bit版バイナリが生成されます。 ただし VCのバージョンによっては64bit用のコンパイルが可能なように色々と設定しておく必要があるかもしれません。 こちらは初心者の方には若干敷居が高いです。 32bit版バイナリでも64bit Windows上で動きますから、特に拘りのない方は32bit版のバイナリで十分です。

参考: 32bit Windows上で64bit Windows版Moaiはビルド可能?
大抵の場合、今使っているOS上で動くMoaiをビルドしたい場合がほとんどかと思います。 しかしなんらかの理由で、32bit Windows上で64bit Windows版Moaiをビルド(クロスコンパイルと呼ばれますが)したいという方もおられるかもしれません。

このようなMoaiのビルドの仕方も、VCとOSの種類によっては可能な場合があります。 クロスコンパイルを行うにはVCの方でクロスコンパイラが適切に提供されていなければなりません。 特に古いWindowsや古いVCの場合、その辺の事情が複雑です。

Windows2000上でクロスコンパイルする場合:
Windows2000上で動作可能なVisual Studioは2008までです。 そしてこの制限により、Windows2000で64bit Windows版Moaiをクロスコンパイルする方法は、無償版Visual Studioでは存在しません。 Visual Studio 2005, 2008 の Professional版(有料版)のみ 64bit Windows版を作成するためのクロスコンパイラが完備されています (bin\x86_amd64という名前のフォルダがクロスコンパイラ本体になりますが、これの有無です)。 その他、Windows SDKなどにVCコンパイラが同梱されている場合もありますが、Windows2000で動作し、かつクロスコンパイラが含まれるものは見当たりません。
WindowsXP上でクロスコンパイルする場合:
WindowsXP上で動作可能なVisual Studioは2010までです。 そしてこの制限により、WindowsXPで64bit Windows版Moaiをクロスコンパイルする方法は、無償版Visual Studioでは存在しません。 Visual Studio 2005, 2008, 2010 の Professional版(有料版)のみ 64bit Windows版を作成するためのクロスコンパイラが完備されています (bin\x86_amd64という名前のフォルダがクロスコンパイラ本体になりますが、これの有無です)。 その他、Windows SDKなどにVCコンパイラが同梱されている場合もありますが、WindowsXPで動作し、かつクロスコンパイラが含まれるものは見当たりません。

因みに、Windows XP Professional x64 Edition( いわゆるWindowsXP 64bit版 )上で 64bit Windows版バイナリを普通にコンパイルする(クロスコンパイルではなくネイティブコンパイルする)のであれば、Visual Studio 2010 Express(無償版)でも可能です。 また逆のケース、つまり Windows XP Professional x64 Edition上で 32bit Windows版バイナリをコンパイルすることも同様に可能です (この場合はクロスコンパイルというよりも、正確にはWOW64により32bit版コンパイラを使うことで、普通に32bit版バイナリを生成する形となります)。
Windows7以降でクロスコンパイルする場合:
Windows7以降と書きましたが、おそらくVista以降でも原理的には可能です。

Windows Vista以降ならば、Visual Studio 2012以降が使用可能となります。 そして2012以降ならば、無償版であっても 64bit Windows版を作成するためのクロスコンパイラが完備されています。

ですからMoaiをビルドする場合は特に何も考えることなく、単にMACHINEをx64にすれば32bit Windows上で64bit Windows版Moaiを ビルドすることもできます。 また逆のケース、つまり 64bit Windows上で 32bit Windows版バイナリをコンパイルすることも同様に可能です (この場合はクロスコンパイルというよりも、正確にはWOW64により32bit版コンパイラを使うことで、普通に32bit版バイナリを生成する形となります)。

Close

デフォルトでは、ディレクトリ src の一つ上の階層にディレクトリmoai-v2.* が生成され、 コンパイルされた実行バイナリや設定ファイルなどの必要なものすべてが、そのディレクトリへとインストールされます。 特に実行バイナリなどプラットフォームによって異なるものはディレクトリ moai-v2.*\bin\$PLATFORM へ格納されます。 ここで$PLATFORMの値は、MACHINEの値が x86 の場合 windows-x86、MACHINEの値が x64 の場合 windows-x64 となります。

参考: インストールディレクトリを変更したい場合
環境変数ZNK_INSTALL_DIRでインストールされるディレクトリのパスを明示的に指定することもできます。 その場合、指定したディレクトリへインストールされます。


以上でWindows版のMoaiのコンパイルとインストールはすべて完了です。 お疲れ様でした。

参考: VCの自動認識
znk_project のビルドシステムは、特に環境変数を設定せずともPCにインストールされているVCを自動認識できることがあります。 特にVC6.0以上VS2015以下のx86環境向けで、デフォルトの位置にインストールされている場合ならこれはうまく機能する可能性があります。 しかしVS2017以降ではVCの環境変数系とディレクトリ構成が大きく変わっており、おそらくこれはうまく機能しません。

Moai Ver2.2以前では、この自動認識システムでVCによるコンパイルを賄っていました。 しかしMoai Ver2.3以降では、既に述べた3つの環境変数を指定する方法を正式な手順として採用しています。

参考: どうやって自動認識しているのか?
内部的にはこの自動認識は znk_project\src\bat_utils\setenv_vc.bat というbatファイルにおいて行われます。 上記の手順で失敗する場合、この自動認識が失敗していると考えられます。

古いVCの場合、VS{Ver}COMNTOOLSという名前の環境変数がVCインストール時に自動的に設定されているはずです ({Ver}にはVCの内部バージョン番号が入ります)。 例えばVisual Studio 2012の場合ですと、( 2012ではVCの内部バージョンは11.0になりますので )環境変数名は VS110COMNTOOLS です。

そしてその値は

C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\Tools

などとなっています. これはVCのインストールディレクトリのパスを意味しており、src\bat_utils\setenv_vc.bat はこの値を参照します。 この値により、以下で述べますVC付属batファイル(ここでは説明のためそう呼んでいますが一般的な呼称ではありません)の位置も明らかとなります。

VCの場合、いくつかの必要な環境変数をあらかじめ設定しておく必要があります。 しかしこれらを手動で設定するのはかなり大変なので(特に環境変数PATHが)、替わりにVCに付属している(はずの)変数設定用batファイルを実行させます。 このbatファイルがVC付属batファイルです。

VC付属batファイルはVCのインストールディレクトリ配下に存在するのですが、厄介なことにVCのバージョンやマシン環境によってそのありかやファイル名が異なるのです。 例えばVC11.0のx86環境向けなら、これはvsvars32.bat という名前になっており、

C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\Tools

などといったディレクトリ内に存在します. あるいは

C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\bin

などといったディレクトリ内に同様の(しかし名前は異なる)ファイルが存在することもあります。 またx64環境(VCではamd64やx86_amd64などと表記されます)向けの場合、詳細は省略しますが状況はさらに複雑です。

さらに言えば、VC付属batファイルは内部でレジストリ情報を参照するため、VC本体の位置が変わった場合などに柔軟に対応できません。 このように色々と厄介な問題があることもあり、Moai Ver2.3 以降ではこれを使うことを基本的にはやめ、detect_vc.batという独自の検出スクリプトを開発導入しました。

Close


Close


目次に戻る

Windows版をコンパイルする(Cygwinを使用)

難易度:★

必要なコマンド群


WindowsでC言語をコンパイルするならCygwinだ!という方もおられると思います。 このセクションではそのような方向けに解説します。

注意: Cygwin上におけるMinGW
Cygwinではややこしいことですが、この上で動作するMinGWをインストールすることもできます。 その場合gccオプションとして -mno-cygwinを使うことで(Cygwin版gccではなく)MinGW版gccを呼び出す形になります。

しかしこの記事では、そのような使い方については対応しません。 つまり作成を目指す実行バイナリは純粋なWindows APIをベースとしたプログラムではなく、cygwin1.dllなどのCygwin固有のdllをベースとしたUNIXエミュレーションで動作するプログラムとします(Cygwinは本来後者のために生まれたものでしょう)。

MinGWでWindows純正のプログラムとしてコンパイルしたい場合は、上記WindowsでMinGWを使ってコンパイルするを参照して下さい。

あるいはMSYSを使うという手もあります。これならばCygwinとある程度は似た使い勝手となります。 下記MSYSを使ってコンパイルするを参照して下さい。

どうしてもCygwin上でMinGWを使いたい方は MSYS の状況に一番近いことから考えて、Makefile_msys.makを各自修正するのが一番簡単であると思います。 生成されるdll名の微調整やコマンドラインオプション -mno-cygwin を追加する程度の変更で済むと思います。

Close


イントロダクションでは、C/C++での開発において最低限必要となる基本コマンドについて述べました。

ここで今一度、Cygwin環境におけるこれらのコマンドについて確認いたしましょう。

ツールの種類 対応コマンド 対象ファイル
Cコンパイラ gcc c から o へ
C++コンパイラ g++ cpp から o へ
リンカ ld o から 実行バイナリ/dllへ
ライブラリアン ar および ranlib o から aへ
ビルドスクリプトmake ( GNU make )Makefile_cygwin.makを使用
標準ライブラリ GNU Cライブラリ システム(/usr配下など)


Cygwinインストーラ(setup-x86.exe)では、gcc-core, gcc-g++, make, binutilsをインストールしておきます ( binutilsとは、ld, ar, ranlib, asコマンドなどを含むパッケージです)。 setup-x86.exe においてこれらを個別にインストールしてもよいですが、Devel カテゴリにこれらは全て含まれているので面倒ならこのカテゴリごと一括でインストールしてもよいです。

Moaiのコンパイルおよびインストール


Cygwinターミナルを開き、srcディレクトリ内の以下のシェルスクリプトを実行します。

sh ./make_world.sh install


上記の基本コマンドが適切にインストールされているならば、これですべてのコンパイルが自動で行われます。

このシェルスクリプトにより、Makefile_cygwin.mak の存在する各ディレクトリへ自動的に移動しつつmake -f Makefile_cygwin.mak が実行されます。 コンパイル後の実行バイナリは各ディレクトリ内のout_dirに格納されます。

尚、Cygwinの場合、出来上がるdllの名前にはcygプリフィックスをつける仕様としてあります。 これは別に必須というわけではないのですが、Cygwin用のdllはVCやMinGWにより生成する普通のdllとは一部異なるため、区別するためにこのようにしてあります.

さらに、srcディレクトリの一つ上の階層にmoai-v2.*-cygwin32ディレクトリが生成され、コンパイルされた実行バイナリや設定ファイルなどの必要なものすべてが、そのディレクトリへとインストールされます。 (なんらかの理由でこのインストール処理だけを省略し、つまり純粋にコンパイルだけを行いたい場合は、「sh ./make_world.sh install」の替わりに「sh ./make_world.sh」と実行してください)

以上でMoaiのコンパイルとインストールはすべて完了です。

動作確認


念のため、コンパイルおよびインストールされたmoaiが起動するか確認しておきましょう。 srcディレクトリを出てmoai-v2.*-cygwin32ディレクトリへと移動し、sh moai_for_cygwin.sh を実行します。 以下のようなメッセージが表示されればmoaiは無事起動しています。

Moai : config load OK.
Moai : target load OK.
Moai : analysis load OK.
Moai : Filter Loading [filters/….myf]
Moai : Plugin Loading [plugins/….so]
…
Moai : acceptable_host=[LOOPBACK]
Moai : blocking_mode=[0]
Moai : AutoGet PrivateIP=[192.168.…].
Moai : Listen port 8124...

Moai : ObserveR : 1-th sock events by select.


目次に戻る

Windows版をコンパイルする(MSYSを使用)

難易度:★

はじめに


ここではMSYS1.0を使用したMinGWによるコンパイル手順を示します。 そもそも MSYS などなくても MinGW だけあれば Moai をビルドすることも普通にできますが、 この章は MSYS 上で MinGW を使いつつ Moai をビルドしたいという方のための解説となります。

MSYS とは別に MSYS2 と呼ばれるものもあります。 ここで説明するのは MSYS2 ではなく MSYS であることにご注意ください。

尚、MSYS2 のインストールは基本的にお勧めしません。 もしもこれから MSYS と MSYS2 のどちらをインストールしようかと迷われている方は MSYS の方をインストールしてください。

参考: MSYS2について
長らくこの記事では MSYS2 の説明を保留してきたのですが、結局 MSYS2 は非推奨という形にしました。

非推奨とした理由
その理由は色々ありますが、例えば以下の通りです。

  1. MSYS はほぼ全てのWindowsで動作するのに対し、MSYS2 は最近のWindowsでしか動作しない。

  2. この記事執筆時点では、公式サイトによればMSYS2の動作環境はWindows10以降が必要ということになっています。

    一応過去のバージョンが置かれたリポジトリ(例えば https://repo.msys2.org/distrib/i686/)があり、 Windows10よりも古いWindowsで起動できるものもまだ残っています。 しかしながらつい数年前までこのリポジトリでダウンロードできていた古いバージョンが既にかなりの数見当たりません。 どうやらかなり速いペースで古いバージョンがリポジトリから削除されているようです。 この記事執筆時点で、このリポジトリに置かれているバイナリは Windows Vista 以降のみで起動ができるものしか残っていません。

    例えば試しにWindows2000上で usr/bin/cp.exe を起動させると「AcquireSRWLockExclusive」関数なるものの存在が要求されます。 Microsoftのサイトによれば、これはWindows Vista以降でサポートされたAPIであるとのことなので、 少なくとも Windows Vista 以降が前提の実行バイナリであることがわかります。

  3. MSYS と比べ、MSYS2 は全体のサイズが巨大である。

  4. MSYSが最終的に19MBで済むのに対し、MSYS2はアーカイブ解凍時点で250MBから300MBにもなります。

    このサイズからすると MSYS2 の方にはコンパイラやmakeコマンドなど、 ビルドに必要なものが最初から全て同梱されているのだろうと思われるかもしれませんが、 むしろ逆で、なんと肝心なものが何も入っておりません。 これはさすがにディスクサイズの無駄です。

    例えば、MSYSにはmakeコマンドが最初から同梱されていますが、 MSYS2はmakeコマンドが初期状態では同梱されていません。 よって次にこれをパッケージ管理ソフトでインストールしようということになるのですが…。

  5. たとえ起動ができたとしてもパッケージ管理ソフト(pacman)が、 場合によってはうまく動作しないことがあり、 結局これを使えるように調整する手間が余計に生じる可能性がある。

    pacman により makeをインストールする場合、通常以下のように実行します。

    # pacman -S make
    

    ところがこれがうまくいく場合といかない場合があります。 しかもその成否は MSYS2 のバージョンによって異なるのです。

    典型的な状況としてはダウンロードしたMSYS2の内部データベースが古く、 ダウンロード先(ミラーサイトなど)との整合性が取れず、あるいは取得すべきファイルのリンク切れなどが起き、 インストールに失敗するというパターンです。 その場合、次のように実行してこれを更新させなければなりません。

    # pacman -Syy
    

    これで普通は先ほどの「pacman -S make」も成功するようになります。 しかし場合によってはこれ以外にもPGP Keyの取得の失敗により、 エラーが発生することがあり、こちらのエラーが発生した場合かなり厄介です。

    この種のエラーは例えばMSYS2プロジェクトでのパッケージのメンテナが変わった場合などで起こります。 メンテナはそれぞれに固有のKeyを持っていますから、メンテナが変わればそのKeyも変わるわけです。 ところが我々ユーザとしてはそんなKeyの変更など知ったことではないですから、 古いKeyをそのままホームディレクトリ等に保存しているわけです。 こちらもこのKeyを更新しない限り、全てのパッケージのインストール要求は(PGP Keyの不一致より)門前払いされることになります。

    このようなことに頭を悩ますなら、直接 GNU Windowsのサイトから自分で GNU makeをインストールした方が簡単で確実となります。 しかも本家からダウンロードするなら PGP Key などよくわからないものに頼るまでもなく、 当然本物のアーカイブでしょう。

    一方、MSYSのパッケージ管理ソフト(mingw-get)の方は、この種の問題も一切なく正常に動作します。 こちらの方が MSYS2 よりもはるかに古く、公式サイトもほぼないに等しい状況であるにも関わらず、 むしろ安定的に動作するのです。

    MSYS2 のパッケージ管理ソフト(pacman)は、元々Arch Linux上で生まれた「OSという巨大システム向け」のパッケージ管理ソフトです。 結局のところ、たかだかビルド環境に過ぎない MSYS2 にこれを使うのはいささか大げさなのです。 これにより本来は考えて無くてもよい(本質的でない)エラーに時間と労力を割く羽目になります (そもそも本家本元である Arch Linux 上の pacman でさえ、しばしばトラブルは起こります)。

    一方で MSYS のmingw-getはこれに比べればはるかにシンプルです。 しかしシンプルだからこそ無駄もなく問題が発生する確率もまた低いのです。

  6. MSYS と比べ、MSYS2 はmount/umountコマンドが期待通りの動作をしない。

  7. これは上記と比べるとそこまで大きな問題ではありませんが、注意は必要です。

    MSYSでは /etc/fstab.conf ファイルを修正後、mount/umountコマンドなどを実行すれば設定が反映されます。

    一方、MSYS2では /etc/fstab.conf ファイルではなく /etc/fstab ファイルを使うのですが、 このファイルを修正後、mount/umountコマンドなどを実行しても設定が正常に反映されません。 MSYS2 で設定を反映させるには一旦コンソールウィンドウごと閉じ、新しいコンソールウィンドウを開く必要があります。

    まあこれ自体は別に大した手間ではないのですが、例えば直前に環境変数PATHなどを一時的に設定していた場合は、 コンソールウィンドウを一旦閉じますので全部やり直しになります。
Close


とはいえ、既に MSYS2 を使っている方もおられると思いますので、 そのような方のために MSYS2 上で Moai/Raraku/Easter をビルドするための指針を述べます。 基本的には MSYS2 でも MSYS と同様の手順でビルド・インストールを行います。

  • Makefile としては Makefile_msys.mak を使用します。
  • GNU make コマンドは使える状態にしておく必要があります (mingw32-makeではなく、単にmakeと入力して実行できる状態である必要があります)。

  • MinGW が使用できる状態にしていなければなりません (単に gcc と入力して実行できる状態である必要があります)。
  • 尚、llvm_clangは現時点では不可です。

  • 全体を一括ビルド・インストールするにはMSYSと同様に znk_project/src/make_world.sh を実行します。
Close

必要なコマンド群


イントロダクションでは、C/C++での開発において最低限必要となる基本コマンドについて述べました。

ここで今一度、MSYS環境におけるこれらのコマンドについて確認いたしましょう。

ツールの種類 対応コマンド 対象ファイル
Cコンパイラ gcc c から o へ
C++コンパイラ g++ cpp から o へ
リンカ ld o から 実行バイナリ/dllへ
ライブラリアン ar および ranlib o から aへ
ビルドスクリプトmake ( GNU make )Makefile_msys.makを使用
標準ライブラリ MinGW WinAPI システム(/mingw配下など)


MSYSは通常MinGWと組み合わせて使うものですから、必要なコマンド群もMinGWと一致します。 そしてMinGWとMSYSでは、これらのコマンドはすべて標準で付属します。 MinGWをまだインストールされていない方は、必要であればまずはWindows版をコンパイルする(MinGWを使用) の「MinGW32のインストール」の部分をご一読の上、先にMinGWのインストールを済ませて下さい。

MSYSのインストール


ここでは MSYS そのもののインストール方法について述べます。 既に MSYS をインストールされ、しかも make コマンドと gcc コマンドが使える状態になっている方は読み飛ばして構いません。

この記事執筆現在、MSYSの公式サイトはMinGW32と同じくhttps://sourceforge.net/projects/mingw となっています。

ただしこの sourceforge は若干サイトの構造がわかりにくいので、インストーラ(mingw-get)が置かれているURLを一旦以下に示します。

https://sourceforge.net/projects/mingw/files/Installer/mingw-get/

ここにはいくつかのバージョンのインストーラ(mingw-get)がリストされていますが、 この記事執筆時点では mingw-get-0.6.2-beta-20131004-1 が一番新しいようですので、これを選びます。 さらに圧縮形式が zip のもの、すなわち mingw-get-0.6.2-mingw32-beta-20131004-1-bin.zip を選びダウンロードしましょう。

このファイル(mingw-get-0.6.2-mingw32-beta-20131004-1-bin.zip)をダウンロードしましたら、 最終的にMSYSをインストールしたいフォルダへこのファイルを移動します (あるいは最初からMSYSをインストールしたいフォルダにこれを直接保存してもよいでしょう)。

例えばフォルダ C:\msys_dir 配下へMSYSをインストールしたい場合、 このzipファイルをC:\msys_dir直下へ移動します。

移動先のフォルダでこのzipファイルを解凍し、 展開フォルダの名前を v1 に変更しましょう (この名前が必ず v1 でなければならないというわけではありませんが、 ここではインストールされるバージョンにちなんだわかりやすく短めの名前としています)。

例えば解凍の結果、フォルダ C:\msys_dir\mingw-get-0.6.2-mingw32-beta-20131004-1-bin が出来上がったとします。 このフォルダ mingw-get-0.6.2-mingw32-beta-20131004-1-bin の名前を v1 に変更します。

コマンドプロンプトを開き、展開ディレクトリ直下にあるディレクトリ bin へ移動します。 またこのディレクトリ bin の直下に mingw-get.exe が存在することを確認してください。

例えば今回の例の場合、コマンドプロンプトから以下のように実行します。

cd C:\msys_dir\v1\bin
dir

最後の dir コマンドの実行により、mingw-get.exe が表示されることを確認します。

mingw-get.exe をコマンドラインより以下のように実行します。

mingw-get install msys-base

これで必要なファイルがすべて v1\var\cache\mingw-get\packages 内に自動的にダウンロードされます。 またダウンロードが終わった後は、ディレクトリ v1 の直下にディレクトリ msys が作られ、 その中に必要なファイルがすべて自動的にインストールされます。 今回の例の場合、この段階でディレクトリ C:\msys_dir 全体として 23MB ほどになります。 ただしインストール終了後は C:\msys_dir\v1\var\cache\mingw-get\packages は不要となりますから、 これをまるごと削除して構いません。 これを削除した場合、最終的には19MBほどになります。

これで MSYS 本体のインストールは終わりです。 今回の例の場合、C:\msys_dir\v1\msys\1.0\msys.bat というファイルが出来ているはずですので これを直接実行すれば、MSYSのコンソールウィンドウが起動します。 あるいは、このmsys.batのショートカットをデスクトップ上に作っておいてもよいでしょう。

MSYSのコンソールウィンドウを起動しましたら、 まずは make コマンドが使えるかどうか、またそのバージョンが3.81以上であるかどうかを確認します。 以下のように実行しましょう。

$ make --version

また上記の結果、次のように表示されることを確認します。

$ make --version
GNU Make 3.81
...

ここで表示される数字が 3.81 以上であれば問題ありません。

万一 sh: make command not found などと表示されたら、何らかの理由で MSYS に付属する make.exe がきちんとインストールされていないということです。 念のため、上記に戻って msys-base をインストールしなおした方がよいでしょう。

次に mount コマンドを実行し、Windows上のどのフォルダが MSYS上のどのディレクトリに相当しているのかを確認します。 以下のように実行しましょう。

$ mount

今回の例の場合、(環境によって多少違いはありますが)おおよそ次のように表示されるはずです。

C:\msys_dir\v1\msys\1.0 on /usr   type user (binmode,noumount)
C:\msys_dir\v1\msys\1.0 on /      type user (binmode,noumount)
C:\msys_dir\v1          on /mingw type user (binmode)
C:\Users\Zenkaku\AppData\Local\Temp on /tmp type user (binmode,noumount)
c: on /c type user (binmode,noumount)

上記の二行目より、Windows上のディレクトリ C:\msys_dir\v1\msys\1.0 が、MSYS上のルートディレクトリ(/) に 相当していることがわかります。 よって、例えば Windows上の C:\msys_dir\v1\msys\1.0\home\Zenkaku が、 MSYS 上の /home/Zenkaku に相当することになります。

次に gcc コマンドを使えるようにします (今回はMSYS本体をMinGW32とは独立して最小構成でインストールしましたので、 gcc コマンドの方は使えないはずです)。

今、C:\mingw32_dir 配下に MinGW32 がインストールされているとしましょう。 このとき、MinGW32 のインストールディレクトリは MSYS のインストールディレクトリの外にあります。 よって MSYS 上で MinGW32 のインストールディレクトリを認識させるには、 その位置を MSYS に教えねばなりません。

C:\msys_dir\v1\msys\1.0\etc\fstab.conf をお好きなテキストエディタで開きましょう。 もしもこれが存在しなければ、まず同フォルダ内の fstab.sample を fstab.conf と言う名前でコピーしてそれを開きます。

このディレクトリには単に fstab という名前のファイルもあるかもしれませんが、 開くべきファイルはそちらではありません。 必ずくfstab.confという名前のファイルでなければなりません。

このファイルでは # で始まる行は単なる説明文なので無視してよく、それ以外の行に注目します。 次のような行があると思います。

c:/msys_dir/v1 /mingw

二つ値がありますが、左側が Windows での実体を表すパス、右側が MSYS 上でそれがどのディレクトリに相当するかを表すパスとなります。 これを以下のように修正します。

c:/mingw32_dir /mingw

左側をMinGW32が実際にインストールされているディレクトリのパスに書き換えます (ただしディレクトリの区切り記号としては「/」を使用してください)。 一方、右側は /mingw のままにしておきます。 書き換えたらこのファイルを上書き保存します。

上記の fstab.conf に書かれた内容を反映させるには、MSYS 上で mountコマンドを次のように実行する必要があります。

$ mount /mingw

万一 '/mingw' is already mounted などと表示されたら、一旦 /mingw をアンマウントしてから、再度マウントした方がよいと思われます。 次のように実行しましょう。

$ umount /mingw
$ mount /mingw

将来、MinGW32フォルダの位置を移動したり、MinGW64 を使うように変更するなどして /etc/fstab.conf の内容を書き直したらこのアンマウント/マウントを再度実行する必要があるので注意しましょう。

これで gcc が無事起動できるようになったはずです。 gcc と単独で実行したとき、「no input files」などと表示されればOKです。

Moaiのコンパイルおよびインストール


MSYSターミナルを開き、srcディレクトリ内の以下のシェルスクリプトを実行します。

sh ./make_world.sh install


上記の基本コマンドが適切にインストールされているならば、これですべてのコンパイルが自動で行われます。

このシェルスクリプトにより、Makefile_msys.mak の存在する各ディレクトリへ自動的に移動しつつ make -f Makefile_msys.mak が実行されます。 コンパイル後の実行バイナリは各ディレクトリ内のout_dirに格納されます。

さらに、srcディレクトリの一つ上の階層にmoai-v2.*-win32ディレクトリが生成され、 コンパイルされた実行バイナリや設定ファイルなどの必要なものすべてが、そのディレクトリへとインストールされます。

なんらかの理由でこのインストール処理だけを省略し、つまり純粋にコンパイルだけを行いたい場合は、「sh ./make_world.sh install」の替わりに「sh ./make_world.sh」と実行してください。

以上でMoaiのコンパイルとインストールはすべて完了です。 Moaiのインストールフォルダ内の bin/windows-x86 に入り、その中にある moai.exe が起動できるかどうかを確認しましょう。



目次に戻る

Windows版をコンパイルする(BCC55(BorlandC Ver5.5.1)またはDMC(Ver8.57)を使用)

難易度:★★

はじめに


軍曹…、久しぶりだな。私だ。

凄いなキミは…。 最初のMoaiがリリースされてかなりの年月が経過したがまだこれらのコンパイラを使うというのかっ!? ダイヤモンドより硬い意志だな!

ならば我々もその意志の強さに敬意を表し、今回は前回と違いMakefileを用意したぞ。 あとこの章の説明では前回と同じくイチイチ丁寧語は使わないぞ?

ただしznk_projectに同梱しているlibresslについては(その他のほとんどのコンパイラであればこれをビルド可能だが) BCC55/DMC ではこれをビルドすることができない。

残念ながら libressl のソースコードにおいては、BCC55/DMCについて全く配慮されていない。 これらでもビルドできるように libressl のソースコードを修正することは理論的には可能だが、 現実的には修正量が膨大となり非常に難しい。 例えば、BCC55 では long long 型がサポートされていないが、libresslではこれを至る所で使用している。 また、DMC では long long 型はサポートされているものの、DMC付属のC標準ライブラリがかなり独特で それに由来するコンパイルエラーが至るところで発生してしまう。

よってBCC55/DMCのlibresslについては(この記事の趣旨からすれば本来反則だが) ビルド済みの dll を znk_project/mkfalt/prebuild 内に用意したのでそれを利用するか、 ネット上からビルド済みの dll を探してダウンロードするのが現実的な方法だ。 あるいはどうしてもlibresslをソースからビルドしたければ、 これに関してだけは VC(Ver8.0以降かつ32bit版)か MinGW32 などを使用してビルドするしかない。

dll については同じプラットフォーム(今回の場合 windows-x86)であれば、 異なるコンパイラで作られたものでも通常は互換性がある。 例えば、VC(x86用)や MinGW32 でビルドしたものを BCC55 や DMC で使うこともできる。 一方、CPUのビット数が異なるような場合、例えばVC(x64用)や MinGW64-64 でビルドしたものを BCC55 や DMC で使うことはできない。 BCC55 や DMC では基本的に 32bit版の dll を必要とするからだ。

尚、最悪libresslなしでもMoai/Raraku/Easterをソースコードからビルドし、動作させること自体は可能ではある。 なぜなら Moai/Raraku/Easter では libressl をコンパイル時にリンクせず、 実行時に libressl の dll(libtls-17.dll)を動的ロードする仕組みとなっているためだ(このdllが存在しない場合は単に無視する)。 ただしその場合、https による通信はできないものとなる(httpのみのサポートとなる)。

こんなの選ぶくらいだ。 今更基本知識の説明はいらんな?

通常、Makefileはソースコードと同じディレクトリに置く。 だが、こんなマイナーなもののMakefileまで用意してはかさばるのでソースコードと同じディレクトリには配置していない。 ただし、今回 Ver2.3 のビルドシステム(mkfgen)において、 私はソースコードがあるディレクトリとは別のディレクトリでビルドできる方式(mkfalt)もサポートした。 znk_project/src/mkfalt 配下にbcc55とdmcのMakefileを置いてあり、 今回、キミはこれを使うことができる。 詳細は後述しよう。

よって以下を参考に各自Makefileを作る必要はないが、 もはやこの記事のシリーズの伝統なので書いとくぞ?

BCC55(Borland C++ Compiler 5.5) における必要なコマンド群


こんなの選ぶくらいだ。 知ってると思うが一応書いとくぞ?

ツールの種類 対応コマンド 対象ファイル
Cコンパイラ bcc32 c から obj へ
C++コンパイラ bcc32 cpp から obj へ
リンカ ilink32 obj から 実行バイナリ/dllへ
ライブラリアン tlib obj から libへ
ビルドスクリプトmake ( GNU make )mkfalt/bcc55配下を使用
標準ライブラリ BCC Cライブラリ bccコンパイラ付属


ビルドスクリプトについてだが、一応BCCに付属しているmakeコマンドがあるにはある。 VCのnmakeと互換であるようだが、微妙に完全互換ではない。 今回用意したMakefileはこれではなく GNU Makefile 用に作ってある。 よって、別途 GNU make(gmake)をインストール(方法は後述)しておく必要がある。

BCC55の入手先だが、昔は Borland からフリーでダウンロードできていたが、 今は Embarcadero 社に権利が移り、これはダウンロードできないかもしれない (別のバージョンを同社のサイトからダウンロードできるかもしれないが、これには登録が必要な上、 BCC55とは大きく異なる点があるため、多分今回用意した我々のMakefileではビルドできない)。

とりあえず現時点では、代替として東京電機大学のサイトhttps://www.mlab.im.dendai.ac.jp/ic2/webdesign/web/tool/bcc/ からダウンロード可能なようだ。

あるいは、万が一上記のリンクが切れた場合、古い本だが、「アドベンチャーゲームプログラミング―美少女ゲームの作り方(坂本千尋著)」 という本の付録CD-ROMが入手できるなら、そこに 美少女Cコンパイラ55 BCC55 も付属しているはずだ! 古本屋で運よくCD-ROM付きのものにエンカウントしたらゲットするがよい。

BCC55におけるコンパイルとそのオプション指定
  • objを作る場合の指定方法

  • これは何てことはない至って普通な感じだ。

    bcc32 -w -RT -tWM -c -oYourFile.obj YouFile.c
    

    ただし「-tWM」オプションには注意が必要だ。 これはマルチスレッド対応C標準ヘッダを使用してコンパイルするためのオプションとなる。 後で述べるが、今回、exe や dll の作成において、マルチスレッド対応C標準ライブラリ(cw32mt.lib)を リンクするようにしており、これに呼応してコンパイルオプション「-tWM」が必要となる。

    この「-tWM」を指定しなかった場合、コンパイラでの指定はシングルスレッド、 リンカでの指定はマルチスレッドということで両者が整合しない。 実は大抵の場合、これでも結果的には問題ないのだが、 標準関数で用意されているものの中にはこの不整合によって問題が起きるものもある。

    例えば Znk_sys_errno.c 内にあるグローバル変数 errno に関しては問題となる。 BCC55では errno はマクロとなっており、 シングルスレッドのヘッダかマルチスレッドのヘッダかでその定義内容が変わる。 シングルスレッドのヘッダの場合、シングルスレッド用のライブラリにしか存在しない関数などが定義されているため、 これが影響して最終的にリンカでエラーが生じることになる。
    繰り返しになるがこの「-tWM」オプションは、マルチスレッド対応C標準ヘッダを使用してコンパイルするためのオプションである。 ただしネット上にある最新の BCC の解説(特に現在の公式サイトであるembarcaderoの解説)では、 これに相当するオプションとして「-tM」が紹介されているため、特に注意が必要だ。 「-tM」オプションは、新しいBCCでのみ使用可能であり、 今回対象としている古いバージョン(Ver5.5.1)のBCCでは使用できない。

  • exeを作る場合の指定方法

  • OBJSは、+記号で各objファイルを連結したものを指定するという変態仕様である。

    ilink32 -w -Gn -C -Tpe -ap c0x32.obj $(OBJS) $(LFLAGS),YourApplication.exe,,import32.lib cw32mt.lib,,YourResource.res
    

    尚、c0x32.obj は、コンソールアプリを作成する場合に必要となる。 またcw32mt.lib は、一つ前で述べたようにマルチスレッド対応C標準ライブラリである。

  • 静的ライブラリ(lib)を作る場合の指定方法

  • オプション /P4096 はページサイズとなり、OMF形式ではこれを明示的に指定する必要がある。 OBJSは、+記号を各objファイルのプリフィックスとして指定するというこれまた変態仕様である。

    tlib YourStaticLib.lib /P4096 $(OBJS)
    

  • dllおよびそれに対応するインポートライブラリを作る場合の指定方法

  • インポートライブラリはimplibを使用してdllから自動生成する。

    ilink32 -w -Gn -C -Tpd c0d32x.obj $(OBJS) $(LFLAGS),YourDLL.dll,,import32.lib cw32mt.lib,YourDLL.def
    implib -f -a YourDLL.lib YourDLL.dll
    

    exe のときとは異なり、「-Tpe -ap」ではなく「-Tpd」を指定することに注意しよう。 また dll ではエクスポートするシンボルを記述した defファイルが必要となる。

    BCC55 の場合、defファイルの作り方が若干複雑である。 BCC55 で作成したオブジェクトファイルでは内部シンボル名に「_」が付くため、 dllでエクスポートするシンボル(関数名)では、この「_」を除去する必要がある。 それゆえ、defファイルの EXPORTS セクションは「X = _X」といった書式で記述する (=の右側がオブジェクトファイルでの内部シンボル名、左側がdllで公開する関数名である)。 例えば以下のような記述となる。

    LIBRARY "kau.dll"
    EXPORTS
    	KauCGI_init       = _KauCGI_init
    	KauHtpBoy_init    = _KauHtpBoy_init
    	KauHtpUtil_init   = _KauHtpUtil_init
    	KauMyf_init       = _KauMyf_init
    	KauPrim_init      = _KauPrim_init
    	KauVar_init       = _KauVar_init
    	RrkExtension_init = _RrkExtension_init
    

    尚、znk_projectでは最初に gslconv という補助ツールをビルドするが、 このツールに「-d2」オプションを与えると、ソースコード内にあるgsl.myfをgsl.defに変換する。 さらに gslconv の4番目の引数に「%%=_%%」と指定すると、上記のような書式のdefファイルを自動的に生成する。

    gslconv -d2 gsl.myf gsl.def %%=_%% 2.3
    

    dllの生成については以上で問題ない。

    最後に dll からインポートライブラリを生成しなければならないが、 インポートライブラリはリンカから見ればオブジェクトファイルと同様に扱われるため、 内部シンボル名もオブジェクトファイルと同様に先頭に「_」を付けた形にしておかなければならない。

    先ほど作った dll の関数名には先頭に「_」が付いていないので、implibを何のオプションも付けずに実行すると、 生成されたインポートライブラリの内部シンボル名も dll 同様に先頭に「_」が付いていないものになってしまう。 そこで、implib を実行の際には「-a」オプションを付け、 先頭に「_」を強制的に付加するように指定する必要がある。

Close


DMC(Digital Mars C/C++) における必要なコマンド群


こんなの選ぶくらいだ。 知ってると思うが一応書いとくぞ?

ツールの種類 対応コマンド 対象ファイル
Cコンパイラ dmc c から obj へ
C++コンパイラ dmc cpp から obj へ
リンカ link(optlink) obj から 実行バイナリ/dllへ
ライブラリアン lib obj から libへ
ビルドスクリプトmake ( GNU make )mkfalt/dmc配下を使用
補助ツール implib dmc(github版) コンパイラ付属
標準ライブラリ DMC Cライブラリ dmc(github版) コンパイラ付属


ビルドスクリプトについてだが、一応DMCに付属しているmake(smake)コマンドがあるにはある。 だがあれはかなり独特であり、機能的にもあまり十分ではない。 今回用意したMakefileはあれではなく GNU Makefile 用に作ってある。 よって、別途 GNU make(gmake)をインストール(方法は後述)しておく必要がある。

DMCの入手先だが、いわゆる昔からある公式サイト(www.digitalmars.com)からはもはやダウンロードしない方がよい。 そうではなく、作者である Wallter Bright 氏が新しく用意した github https://github.com/DigitalMars からダウンロードしよう。

昔からある公式サイト(www.digitalmars.com)の方においてある zip ファイルは一部のファイルが欠けているため問題がある。 例えば、Moaiでは必須となるネットワーク系のシステムライブラリ ws2_32.lib が存在しない。

また、dllからインポートライブラリを作成するツールである implib は www.digitalmars.com では Basic Utilities に含まれると記載があるが、 この Basic Utilities がおそらくリンク切れを起こしており、 そこからはダウンロードできない。
この新しくできた github(https://github.com/DigitalMars)が第三者による偽者ではなく、 Wallter Bright 氏本人のものであることは、以下のようにして確認できる。

  1. いわゆる昔からある公式サイトhttps://www.digitalmars.comへ行く (このサイトは当然ながら本人の公式サイトである)。
  2. サイトの一番上の辺りに「New」があるが、 ここに「Digital Mars on Github」というリンクがある。 このURLを確認すればよい。 これは確かに「https://github.com/DigitalMars」となっており、 間違いないことが分かる。

dmc 本体のリンクも以下に示しておこう。 要するにこれをダウンロードすればすべてが揃う。

DMC本体(153MB) : https://github.com/DigitalMars/dmc/archive/refs/heads/master.zip

尚、上記はソースコードとコンパイル済みバイナリまで全てを含む(よって少しダウンロードサイズは大きめとなっている)。 また展開後のサイズは439MBほどになる。

binディレクトリ内にはコンパイラを始めとしたすべてのツール、 libディレクトリ内には標準的なインポートライブラリが一通り揃っているので これを利用しよう。 尚、helpディレクトリ(72MB)は明らかに無駄なのでただちに削除してよい (そもそも最近のWindowsではコンバートしない限りこの中身を閲覧できないため不要である)。 ドキュメントを見るにしてもhtmlディレクトリあるいは公式サイトにあるもので十分である

DMCにおけるコンパイルとそのオプション指定
我々が実際にテストしたのは執筆時点での最新バージョン(Ver8.57)のみである (DMCの最新バージョンはここ10年くらいずっとVer8.57で変わっていない)。

  • objを作る場合の指定方法

  • イキナリ吐きそうなほどのオプション指定の応酬である。

    だがこんなの選ぶくらいだ。オプションの大名行列くらい何てことないな?

    dmc -HP99 -Bj -j0 -Ab -Ae -Ar -w6 -c -oYourFile.obj YouFile.c
    

  • exeを作る場合の指定方法

  • カンマがウザいあたりはBCCと同じだ。

    link -EXETYPE:NT -SUBSYSTEM:CONSOLE:4.0 -ENTRY:mainCRTStartup $(OBJS), YourApplication.exe,, mydll.lib ws2_32.lib user32.lib kernel32.lib,,
    

    コンソールアプリの場合、サブシステムとして「-SUBSYSTEM:CONSOLE:4.0」の指定と エントリポイントとして「-ENTRY:mainCRTStartup」の指定が必要となる。

    尚、BCC55とは異なり、OBJSを「+」で連結するとかそういうことは必要ない (ただし「+」で連結したい変態はしても構わない。 DMCの場合どちらでもよいということだ)。 これは以下で述べる静的ライブラリ、動的ライブラリの指定におけるOBJSでも同様だ。

  • 静的ライブラリ(lib)を作る場合の指定方法

  • オプション -p4096 はページサイズである。 OMF形式ではこれを明示的に指定する必要がある。

    lib -c -p4096 YourStaticLib.lib $(OBJS)
    

  • dllおよびそれに対応するインポートライブラリを作る場合の指定方法

  • exeの生成と同じく、以下のように link(optlink)を使う。

    link -EXETYPE:NT $(OBJS) dll_main.obj, YourDLL.dll,, ws2_32.lib user32.lib kernel32.lib, YourDLL.def
    implib /system YourDLL.lib YourDLL.dll
    

    ただしdllの場合、色々と注意しなければならないことがある。 まず exe とは違い、サブシステムとエントリーポイントの指定は不要である。

    さらにdmcの場合、DllMainが定義されたobjファイル(これをdll_main.objとする)が一つのdllにつき一つだけ必要である。 これを指定しない場合でも何も問題なかったかのように処理は進み、dllは作られてしまう。 このdllを使って別のexeとリンクもできてしまう。 そこまですべてがうまくいったかのように見えてしまうが、これは罠だ、軍曹!。 このexeを実行しようとした最後の最後で、何と落ちるのだ

    気をつけたまえ!軍曹! 途中で「DllMainが見つかりません」などといったエラーも特に出ないため、 知らないと原因がわからず嵌るぞ(私が)。

    あまりないことだとは思うが、一つのdllを作成するソースコード群の中に DllMainが定義されたc言語ファイルが二つ以上ある場合、 当然だがリンク時に「DllMainが二重に定義されている」といった旨のエラーが表示される。

    実は libZnk では、その配下にzlibも内包しており、 libZnk/dll_main.c と libZnk/zlib/dll_main.c の二つのdll_main.cが存在する。 DMC の場合、dll_main.c では DllMain を定義するようになっており、 つまり上記で述べたように二重にDllMainが定義されてしまう状況になる。

    libZnk でこのようにしている理由は、一応 libZnk/zlib だけを単独ビルドすることも可能としているためだが、 一方で libZnk 全体としてビルドした場合、libZnk/zlib の方の DllMain が存在しては問題となる。 そのため、libZnk の Makefileでは、DMCの場合に限るが libZnk/zlib のコンパイルオプションとして -DDLLMAIN_DEFINED を指定してある。 この DLLMAIN_DEFINED マクロの指定により、dll_main.c 内でのDllMainの定義が抑制される。

    また dll ではエクスポートするシンボルを記述した defファイルが必要となる。 BCC55 の場合とは異なり、DMCのlinkコマンド(optlink)で生成される dll は、 defファイルで(特に凝った記述をせずとも)オリジナルの関数名そのまま書けば、 公開関数名は「_」が付加されていない状態で定義される。 例えば以下のような記述でよい。

    LIBRARY "kau.dll"
    EXPORTS
    	KauCGI_init
    	KauHtpBoy_init
    	KauHtpUtil_init
    	KauMyf_init
    	KauPrim_init
    	KauVar_init
    	RrkExtension_init
    

    尚、znk_projectでは最初に gslconv という補助ツールをビルドするが、 このツールに「-d2」オプションを与えると、ソースコード内にあるgsl.myfをgsl.defに変換する。 さらに gslconv の4番目の引数に「%%」と指定すると、上記のような書式のdefファイルを自動的に生成する。

    gslconv -d2 gsl.myf gsl.def %% 2.3
    

    dllの生成については以上で問題ない。

    最後に dll からインポートライブラリを生成しなければならないが、 インポートライブラリはリンカから見ればオブジェクトファイルと同様に扱われるため、 内部シンボル名もオブジェクトファイルと同様に先頭に「_」を付けた形にしておかなければならない。

    先ほど作った dll の関数名には先頭に「_」が付いていないので、implibを何のオプションも付けずに実行すると、 生成されたインポートライブラリの内部シンボル名も dll 同様に先頭に「_」が付いていないものになってしまう。 そこで、implib を実行の際には「/system」オプションを付け、 先頭に「_」を強制的に付加するように指定する必要がある

    これはBCC55のimplibにおける「-a」オプションと働き、使用理由ともに同じである。

Close


GNU make(gmake)の単独インストール


Windows 版 GNU make は(MinGWにも付属しているが)以下の GNU(GNU Win32)のサイトから単独で入手することも可能だ。

https://gnuwin32.sourceforge.net/packages/make.htm

上記のサイトの「Download」にある以下の二つのファイルをダウンロードしよう。

  • Description「Binaries」のZip
  • Description「Dependencies」のZip

上記のサイトのgmakeのバージョンは(この記事執筆時点では)3.81であり、 ダウンロードされるファイル名はそれぞれ make-3.81-bin.zip と make-3.81-dep.zip のようになっているはずだ (尚、両者のダウンロードサイズは1MBにも満たない)。 これを適当なディレクトリ(例えばC:\gmake_dir)内に保存する。

make-3.81-bin.zip と make-3.81-dep.zip を解凍するとディレクトリ bin や その他のディレクトリが生成されるが、 重要なのはディレクトリ bin のみである。 これら二つの bin にはそれぞれ以下のものが格納されている。

  • make-3.81-bin.zip の方のbin
  • make.exe(gmake本体)

  • make-3.81-dep.zip の方のbin
  • libiconv2.dll と libint3.dll( gmake の起動にあたって必要となる動的ライブラリ)

上記の libiconv2.dll と libint3.dll を make-3.81-bin.zip の方の bin 内に移動しよう。 即ち移動先 bin 内には make.exe、libiconv2.dll、libint3.dll の三つのファイルがある状態になる。 尚、この bin 以外のディレクトリは不要なので削除して構わない。 これでインストール完了だ(なんと総サイズが1MB弱で済む素晴らしいほどのコンパクトさだ)。

一応コマンドプロンプトよりこの ディレクトリ bin へ移動し、make.exe が正常に起動するか否かを確認しよう。 あとはこの make.exe のパス(今回想定している例では「C:\gmake_dir\make-3.81-bin\bin\make.exe」)を環境変数 MAKE にセットすればよい (即ちバッチファイル上で単に「%MAKE%」を実行すると gmake が実行できるような形にセットする)。

BCC55でのビルドとインストール


要は bcc32 などのコマンドがそのまま実行可能な状態で、 znk_project\mkfalt\bcc55 直下にある全てのMakefile(拡張子はmak)に対し、 GNU makeコマンドを実行するだけである。 znk_project\mkfalt\bcc55 内にはこれらを一括で実行するためのバッチファイル make_world.bat があるのでそれを利用しよう。 以下の手順となる。

  1. BCC55がインストールされているパスを環境変数ZNK_BCC55_DIRに設定する。

  2. この値は %ZNK_BCC55_DIR%\Bin\bcc32.exe が存在するような形で設定しなければならない。

    尚、znk_project\mkfalt\bcc55 内にある config_sample.bat を config.bat という名前に変更し、 その中身の以下の行にある ZNK_BCC55_DIR の値を書き変えてもよい。

    REM BCC55 install directory
    set ZNK_BCC55_DIR=C:\bcc55_dir
    

  3. 環境変数MAKEにgmakeそのもののパスを設定する。

  4. この設定は直前のgmakeのインストールで既に行っていると思うが、念のためここでも繰り返し述べておこう。 例えばgmakeの実体が「C:\gmake_dir\make-3.81-bin\bin\make.exe」である場合、 環境変数 MAKE の値をそのまま「C:\gmake_dir\make-3.81-bin\bin\make.exe」とすればよい。

    尚、znk_project\mkfalt\bcc55 内にある config_sample.bat を config.bat という名前に変更し、 その中身の以下の行にある MAKE の値を書き変えてもよい。

    REM GNU make
    set MAKE=..\gmake_dir\make-3.81-bin\bin\make
    

  5. znk_project\mkfalt\bcc55 内へ移動し、make_world.batを実行する。

  6. これですべてのコンパイルおよびインストールが自動で行われるはずだ。

    ただしlibresslについては znk_project/mkfalt/prebuild/ 内のプレビルドdll(libtls-17.dll)を使用する。 これについては我々の環境でVC8.0(VS2005)を使ってビルドしたものである (既に述べた通り、BCC55/DMCでのlibresslのビルドは非常に難しい)。 prebuild.mak という Makefile では、この dll を moai と raraku のインストールディレクトリにコピーする。

    環境変数 ZNK_INSTALL_DIR を指定している場合はそこにインストールされる。 特にコンパイルされた実行バイナリやライブラリなどは、%ZNK_INSTALL_DIR%\moai-v$REL_VER\bin\windows-x86 という名前のディレクトリに配置される。 Rarakuの場合も同様で、%ZNK_INSTALL_DIR%\raraku-v$RRK_VER($RRK_VERはRarakuのバージョン番号で例えば1.0など)という名前のディレクトリ配下にインストールされる。

    尚、環境変数 ZNK_INSTALL_DIR が指定されていない場合は、src ディレクトリの一つ上の階層に install_dir というディレクトリが生成され、 その配下に moai-v$REL_VER や raraku-v$RRK_VER などが配置される。

    このバッチファイルにより、一つ上の make_list.bat 内に記述されたリストを元に、 すべてのMakefile(拡張子はmak)が順番にmakeされる。 コンパイル時の一時的なオブジェクトファイルなどはすべて znk_project\mkfalt\dmc\out_dir 配下に格納される。
    あるライブラリや実行バイナリだけをピンポイントにコンパイルしたい場合は、 以下のようなバッチファイルを作るとよいだろう。

    @echo off
    call _make.bat easter.mak
    pause
    

    上記は easter のみをコンパイルする。

    あるいはあるライブラリや実行バイナリだけをピンポイントにインストールしたい場合は、 以下のようなバッチファイルを作るとよいだろう。

    @echo off
    call _make.bat easter.mak install
    pause
    

    上記は easter のみをコンパイルとインストールする。

DMCでのビルドとインストール


要は dmc などのコマンドがそのまま実行可能な状態で、 znk_project\mkfalt\dmc 直下にある全てのMakefile(拡張子はmak)に対し、 GNU makeコマンドを実行するだけである。 znk_project\mkfalt\dmc 内にはこれらを一括で実行するためのバッチファイル make_world.bat があるのでそれを利用しよう。 以下の手順となる。

  1. DMCがインストールされているパスを環境変数ZNK_DMC_DIRに設定する。

  2. この値は %ZNK_DMC_DIR%\Bin\bcc32.exe が存在するような形で設定しなければならない。

    尚、znk_project\mkfalt\dmc 内にある config_sample.bat を config.bat という名前に変更し、 その中身の以下の行にある ZNK_DMC_DIR の値を書き変えてもよい。

    REM DMC install directory
    set ZNK_DMC_DIR=C:\dmc_dir\dmc-master
    

  3. 環境変数MAKEにgmakeそのもののパスを設定する。

  4. この設定は直前のgmakeのインストールで既に行っていると思うが、念のためここでも繰り返し述べておこう。 例えばgmakeの実体が「C:\gmake_dir\make-3.81-bin\bin\make.exe」である場合、 環境変数 MAKE の値をそのまま「C:\gmake_dir\make-3.81-bin\bin\make.exe」とすればよい。

    尚、znk_project\mkfalt\dmc 内にある config_sample.bat を config.bat という名前に変更し、 その中身の以下の行にある MAKE の値を書き変えてもよい。

    REM GNU make
    set MAKE=..\gmake_dir\make-3.81-bin\bin\make
    

  5. znk_project\mkfalt\dmc 内へ移動し、make_world.batを実行する。

  6. これですべてのコンパイルおよびインストールが自動で行われるはずだ。

    ただしlibresslについては znk_project/mkfalt/prebuild/ 内のプレビルドdll(libtls-17.dll)を使用する。 これについては我々の環境でVC8.0(VS2005)を使ってビルドしたものである (既に述べた通り、BCC55/DMCでのlibresslのビルドは非常に難しい)。 prebuild.mak という Makefile では、この dll を moai と raraku のインストールディレクトリにコピーする。

    環境変数 ZNK_INSTALL_DIR を指定している場合はそこにインストールされる。 特にコンパイルされた実行バイナリやライブラリなどは、%ZNK_INSTALL_DIR%\moai-v$REL_VER\bin\windows-x86 という名前のディレクトリに配置される。 Rarakuの場合も同様で、%ZNK_INSTALL_DIR%\raraku-v$RRK_VER($RRK_VERはRarakuのバージョン番号で例えば1.0など)という名前のディレクトリ配下にインストールされる。

    尚、環境変数 ZNK_INSTALL_DIR が指定されていない場合は、src ディレクトリの一つ上の階層に install_dir というディレクトリが生成され、 その配下に moai-v$REL_VER や raraku-v$RRK_VER などが配置される。

    このバッチファイルにより、一つ上の make_list.bat 内に記述されたリストを元に、 すべてのMakefile(拡張子はmak)が順番にmakeされる。 コンパイル時の一時的なオブジェクトファイルなどはすべて znk_project\mkfalt\dmc\out_dir 配下に格納される。
    あるライブラリや実行バイナリだけをピンポイントにコンパイルしたい場合は、 以下のようなバッチファイルを作るとよいだろう。

    @echo off
    call _make.bat easter.mak
    pause
    

    上記は easter のみをコンパイルする。

    あるいはあるライブラリや実行バイナリだけをピンポイントにインストールしたい場合は、 以下のようなバッチファイルを作るとよいだろう。

    @echo off
    call _make.bat easter.mak install
    pause
    

    上記は easter のみをコンパイルとインストールする。

動作確認


動作確認については他のWindows環境と同じなので省略する。 私からは以上だ。健闘を祈る!



目次に戻る

Linux版をコンパイルする

難易度:★

必要なコマンド群


イントロダクションでは、C/C++での開発において最低限必要となる基本コマンドについて述べました。

ここで今一度、Linux環境におけるこれらのコマンドについて確認いたしましょう。

ツールの種類 対応コマンド 対象ファイル
Cコンパイラ gcc c から o へ
C++コンパイラ g++ cpp から o へ
リンカ ld o から 実行バイナリ/soへ
ライブラリアン ar および ranlib o から aへ
ビルドスクリプトmake ( GNU make )Makefile_linux.makを使用
標準ライブラリ GNU Cライブラリ システム(/usr配下など)


これらのコマンドが実行できるように必要に応じてインストールすればよいということになります。 尚、Linux においてはオブジェクトファイルの拡張子は o、静的ライブラリの拡張子は a、動的ライブラリの拡張子は so となります。 実行バイナリの拡張子はありません。

いわゆるVCにおけるインポートライブラリに相当する拡張子は存在しません。 so がその役割を兼ねるためです。

これらを個別にインストールしてもよいですが、Development系のカテゴリにある基本パッケージをインストールすればまずすべて自動でインストールされるでしょう。 例えば Arch Linux なら pacman で base-devel をインストールする形になります。 具体的なインストール方法はそれぞれのLinuxディストリビューションによって千差万別なのでここでは記載しません。 ディストリビューションによっては最初からインストールされている可能性も高いですね。

コンパイルおよびインストール


上記「基本知識」で述べた必須コマンドはすべてシステムにインストールされているものとして話をすすめます。

  1. ターミナル(xterm, urxvtなど)を開き、インストール先のパスを環境変数 ZNK_INSTALL_DIR で指定します。

  2. export ZNK_INSTALL_DIR=$HOME/install_dir
    

    環境変数 ZNK_INSTALL_DIR で指定されたディレクトリが存在しない場合は自動的に生成されます。 Moaiの場合、$ZNK_INSTALL_DIR/moai-v$REL_VER($REL_VERはMoaiのバージョン番号で例えば2.3など)という名前のディレクトリが生成され、 必要なファイルがそのディレクトリ配下へとインストールされます。

    特にコンパイルされた実行バイナリやライブラリなどは、$ZNK_INSTALL_DIR/moai-v$REL_VER/bin/$PLATFORM という名前のディレクトリに配置されます。 ここで $PLATFORM は実行バイナリの種類を表し、例えばLinuxにおいては、linux-x86、linux-x64 といった文字列になります。 Rarakuの場合も同様で、$ZNK_INSTALL_DIR/raraku-v$RRK_VER($RRK_VERはRarakuのバージョン番号で例えば1.0など)という名前のディレクトリ配下にインストールされます。

    尚、環境変数 ZNK_INSTALL_DIR が指定されていない場合は、src ディレクトリの一つ上の階層に install_dir というディレクトリが生成され、 その配下に moai-v$REL_VER や raraku-v$RRK_VER などが配置されます。

    尚、環境変数INST_MACHINEに「x86」または「x64」を明示的に設定して上記を実行した場合は、それぞれ32bit版または64bit版バイナリが生成されます。 この環境変数を指定していない場合は、Makefile_linux.mak 内で uname -m コマンドが実行され、その結果により 32bit版か64bit版かが自動的に決定されます。

  3. srcディレクトリへ移動し、以下のシェルスクリプトを実行します。

  4. cd znk_project/src
    sh ./make_world.sh install
    

    これですべてのコンパイルおよびインストールが自動で行われます。

    このシェルスクリプトにより、Makefile_linux.mak の存在する各ディレクトリへ自動的に移動しつつ make -f Makefile_linux.mak install が実行されます。 コンパイル後の実行バイナリは各ディレクトリ内のout_dirに格納されます。
    インストール処理だけを省略したい、つまり純粋にコンパイルだけを行いたい場合は、 各々のディレクトリで直接「make -f Makefile_linux.mak」を実行した方がよいでしょう。 「sh ./make_world.sh」と実行することもできますが、 純粋にコンパイルだけでは必要なライブラリやツールがまだ所定の位置に配置されないため、 それらに依存するツールのコンパイルにおいて、リンクに失敗するかもしれません。

以上でMoaiのコンパイルとインストールはすべて完了です。

動作確認


念のため、コンパイルおよびインストールされたmoaiが起動するか確認しておきましょう。 インストールディレクトリ配下のmoai-v$REL_VER ディレクトリへと移動し、moaiを起動します。 例えばMoaiのバージョン番号が2.3の場合であれば以下のようになります。

cd $ZNK_INSTALL_DIR/moai-v2.3
sh moai.sh

以下のようなメッセージが表示されればmoaiは無事起動しています。

Moai : config load OK.
Moai : target load OK.
Moai : analysis load OK.
Moai : Filter Loading [filters/….myf]
Moai : Plugin Loading [plugins/….so]
…
Moai : acceptable_host=[LOOPBACK]
Moai : blocking_mode=[0]
Moai : AutoGet PrivateIP=[192.168.…].
Moai : Listen port 8124...

Moai : ObserveR : 1-th sock events by select.

最初からacceptable_hostをANYの状態(LAN内の他のPCやスマホからもmoaiにアクセスできる状態)にして起動したい場合は、 以下のように -acceptable_any オプションを指定して実行することもできます。

cd $ZNK_INSTALL_DIR/moai-v2.3
sh moai.sh -acceptable_any

これにより、上記の起動メッセージにおいて、 「acceptable_host=[LOOPBACK] 」の部分が 「acceptable_host=[ANY] 」となって起動されるはずです。


目次に戻る

Linux版をコンパイルする(llvm_clangを使用)

難易度:★★

はじめに


Linuxでは基本的にGNU系のコンパイラ(gcc)が使われますが、 最近ではその対抗馬として非GNU系のコンパイラが使われることもあります。 なんらかの理由でGNU系のコンパイラでは問題がある場合、 例えば一番メジャーな代替コンパイラとして llvm_clang があります。 ここではそれを使ったビルドについても説明しましょう。 (ただしここでは完全にGNU系の開発ツールを排除するわけではなく、 リンカやmakeコマンドなど一部のツールはシステム標準の(すなわちGNU系の)ものを使用します)。

LLVMのCコンパイラは実際のファイル名(あるいはシンボリックリンク名)としては「clang」となりますが、 Google検索などでは「C言語」を表すキーワードとして「C lang」などと記述することもあり、 つまり単なる「C言語」的な意味合いの言葉と明確に区別するため、この記事ではこれを「llvm_clang」と表記するものとします。

必要なコマンド群


イントロダクションでは、C/C++での開発において最低限必要となる基本コマンドについて述べました。

ここで今一度、Linux環境(ただしllvm_clang)におけるこれらのコマンドについて確認いたしましょう。

ツールの種類 対応コマンド 対象ファイル
Cコンパイラ clang c から o へ
C++コンパイラ clang++ cpp から o へ
リンカ ld o から 実行バイナリ/soへ
ライブラリアン ar および ranlib o から aへ
ビルドスクリプトmake ( GNU make )Makefile_linux.makを使用
標準ライブラリ GNU Cライブラリ システム(/usr配下など)


これらのコマンドが実行できるように必要に応じてインストールすればよいということになります。

ただし今回の場合、GNU系のツールとllvm_clangの両方を使うのでどちらもインストールしておかなければなりません (これがよくわからないようであれば、そもそも llvm_clang を使う方法ではなく、 既に述べた gcc を使う方法でビルドした方がよいでしょう)。

LLVM付属のリンカやライブラリアンも存在しますが、今回は使用しないものとします (というよりznk_projectに付属するMakefile_linux.makでは、それらを使用するようになっていません)。
通常、llvm_clang はシステムで提供されているパッケージマネージャなどを使ってインストールするのが一番楽と思われますが、 (llvm_clang のバージョンにもよりますが)これによりさらに追加で 500MB 以上のサイズを消費するかもしれません。 またその場合、管理者権限が必要です。

システムへの直接のインストールを避けたい場合、 例えば llvm_clang のソースコードからビルドすることもできますが、これはかなり大変です。 llvm_clang の公式サイトにコンパイル済みバイナリが提供されていますのでこれをダウンロードし、使う方がよいかもしれません。 この方法については「MacOSX(Darwin)版をクロスコンパイルする」というセクションで少し詳しく説明しています。

コンパイルおよびインストール


上記「基本知識」で述べた必須コマンドはすべて所定の位置にインストールされているものとして話をすすめます。

  1. ターミナル(xterm, urxvtなど)を開き、llvm_clangのインストール先を環境変数 ZNK_LLVM_CLANG_DIR で指定します。

  2. 例えば、$HOME/Downloads/llvm_dir/v3.9/install_dir に llvm_clang がインストールされている場合、 以下のように設定します。

    export ZNK_LLVM_CLANG_DIR=$HOME/Downloads/llvm_dir/v3.9/install_dir
    

    念のために補足しますが、上記は「$ZNK_LLVM_CLANG_DIR/bin/clang」と入力すると llvm_clang が実行できる状態にしておくということです。 そのように実行し、「clang-3.9: error: not input file」などといったエラーが出れば成功です。

    llvm_clang の場合でも使用される Makefile は gcc の場合と同じくMakefile_linux.mak となります。 Makefile_linux.mak は環境変数 ZNK_LLVM_CLANG_DIR が定義されている場合、 llvm_clang を使用したビルドに自動的に切り替えるようになっています。

    そのため、この環境変数 ZNK_LLVM_CLANG_DIR の定義は必須です。 たとえ単に「clang」と打ってllvm_clangが実行できるようにPATHを設定していたとしてもです。 例えば llvm_clang が最初からシステムにインストールされている状態( /usr/bin/clang などが存在する状態)であった場合は、 「export ZNK_LLVM_CLANG_DIR=/usr」と設定することになります。

  3. Moaiをインストールする予定のパスを環境変数 ZNK_INSTALL_DIR で指定します。

  4. export ZNK_INSTALL_DIR=$HOME/install_dir
    

    これについての詳細は既に述べた gcc 版でのインストールと同様です。 詳しくはそちらを参照してください。

  5. srcディレクトリへ移動し、以下のシェルスクリプトを実行します。

  6. cd znk_project/src
    sh ./make_world.sh install
    

    これについての詳細は既に述べた gcc 版でのインストールと同様です。 詳しくはそちらを参照してください。

以上でMoaiのコンパイルとインストールはすべて完了です。

動作確認


念のため、コンパイルおよびインストールされたmoaiが起動するか確認しておきましょう。 インストールディレクトリ配下のmoai-v$REL_VER ディレクトリへと移動し、moaiを起動します。 例えばMoaiのバージョン番号が2.3の場合であれば以下のようになります。

cd $ZNK_INSTALL_DIR/moai-v2.3
sh moai.sh

これについての詳細は既に述べた gcc 版でのインストールと同様です。 詳しくはそちらを参照してください。

目次に戻る

Linux版をコンパイルする(musl cライブラリを使用)

難易度:★★★

はじめに


Linuxでは基本的にGNU系のC言語標準ライブラリ(glibc)が使われます。 これは gcc と llvm_clang のどちらを使っても同じです。

しかし最近ではその対抗馬として非GNU系のC言語標準ライブラリが使われることもあります。 その一番メジャーな代替ライブラリとして musl があります。 ここでは musl を使ったビルドについて説明しましょう。

muslはカタカナ的には「マッスル」のように発音するそうです。 助動詞の must が「マスト」のように発音しますが、 それと合わせて覚えておけば忘れにくいかもしれません。
LLVMに付属のC言語標準ライブラリもあるようですが、 現状ではglibcやmuslほどの完成度ではないと言われています。 現状では使用を避けた方が無難でしょう。

実際の手順の説明に入る前に、なぜわざわざGNU系のC言語標準ライブラリ(glibc)を避ける動機が生じるのかについて説明しましょう。

端的に言えば、glibc の場合、静的リンクがし辛く、場合によってはできないからです。 また動的リンクにするにしても glibc を同梱した配布がし辛いからです。

これは技術的な問題というよりもライセンスの問題です。 GNUが設定しているライセンス(GPL)では、glibc を静的リンクしたり同梱して配布した場合、 そのプロジェクトのライセンスもGPLに従わなくてはならなくなるといういわゆる「GPLの汚染」が起こります。

GPL(今回の場合、正確にはLGPL)に従うということは、そのプロジェクトは必ずオープンソースにしなくてはならないということです。 これでは商用利用のプロジェクトにおいて問題があるかもしれません。

最初からオープンソースのプロジェクトの場合は関係ないと思われるかもしれませんが、 これを使う別のプロジェクトにもGPLを汚染させてしまうことがもう一つ問題となります。 これは別の(オープンソースの)プロジェクトがあなたの(GPLに汚染された) プロジェクトを使うことを躊躇する要因にも成りかねません。

GPLは元々自由を保証するためのライセンスであり、歴史的に見ればまだオープンソースがそれほど市民権を得ていなかった時代、 このGPLが事態を打破すべく重要な役割を果たしたのは確かです。 しかしながらもう時代は完全に変わっています。 オープンソースがこれほど隆盛し、さらなる自由なライセンスが主流となっている現在にあっては、 GPLはその本来の思惑とは裏腹にむしろオープンソースの流れを滞らせる要因にも成りかねず、 相対的に「不自由なライセンス」といえるかもしれません。

そのため、glibc を使う場合は通常これを動的リンクの形にした(実体のないシンボルだけの情報を付加した)上で、 glibc の実体としてはインストール先のシステムに最初から配備されているものを使います。 (実体をくっつけた状態では配布していないわけですから)これでGPLに関する問題はクリアされるというのが一般的な見方です。

そのような運用でも GPL を適用すべきという宗教も存在するようですが、 ここではそれについてこれ以上議論しません。

しかし「動的リンクの形にした上で、glibc の実体はインストール先のものを使う」ということは、 「インストール先のglibcのバージョンに依存する」ということです。 そしてもし「ビルド環境での glibc のバージョン」と 「インストール先での glibc のバージョン」が一致しなかった場合、 その実行バイナリは「インストール先での glibc」を正常にロードできず、起動に失敗する可能性があります。

glibc のバージョンの組み合わせによっては起動できるかもしれません。 しかし特に「インストール先での glibc のバージョン」が「ビルド環境での glibc のバージョン」よりも古い場合、 起動に失敗する可能性が高くなります。

Linuxをよく使っている方は、「GLIBC_2 32 not found」といったエラーを見たことがあるかもしれません。 このエラーはglibcのバージョンの不一致を意味しています。

要するに glibc を使った場合、Linuxのバージョンによっては動作しない可能性があるということです。 一方、muslなどの非GNU系Cライブラリの場合、このような同梱を行ってもライセンスの伝染は起こりません (musl自体はMITライセンスです)。 そのため、glibc にあるような問題もなく、 結果的に musl を使った場合、(よほど古いLinuxでない限りは)どんなLinuxのバージョンでも動作させることができるわけです。

ならば最初から musl を使ったビルドを説明すればよいではないかと思われるかもしれませんが、 これを使った場合、何も考えずにビルドする(glibcを使ってビルドする)場合と比べ、 今度は技術的に配慮すべきことが色々とあり、若干難しいのです。 そのため難易度も★3つ(Lv3)としています。

尚、Alpine Linuxではシステムに最初からmuslがインストールされており、 通常、何も考えずに単純にビルドした場合でもmuslを使ったビルドが行われます。 しかし、このようにして作った(Alpine Linux製の)実行バイナリは、通常、他の(非Alpine系の)Linuxでは自立起動できません。 少し工夫をこらした方法で起動する必要があります。

動的ライブラリを全く使わない(完全に静的な)実行バイナリであれば例外的に自立起動できる場合もあります。 ただし静的ライブラリ libc.a を使った場合、musl ライブラリの仕様上の理由から(プラグインなどの)動的ロードができなくなります。

全ての Linux で見かけ上自立起動できるようにするためには、 単に musl を使うだけでなくさらに動的リンカに関する配慮も必要です。 このセクションではそれについて説明しています。

尚、Alpine Linuxでのビルドについては他の(非Alpine系の)Linuxとは異なる独自の注意点がありますので、 別途一つの章を設けて解説しています。

必要なコマンド群


イントロダクションでは、C/C++での開発において最低限必要となる基本コマンドについて述べました。

ここで今一度、Linux環境(ただしgccとmuslを使用)におけるこれらのコマンドについて確認いたしましょう。

ツールの種類 対応コマンド 対象ファイル
Cコンパイラ gcc c から o へ
リンカ ld o/a から 実行バイナリ/soへ
ライブラリアン ar および ranlib o から aへ
ビルドスクリプトmake ( GNU make )Makefile_linux.makを使用
標準ライブラリ musl Cライブラリ znk_project内蔵のmusl


これらのコマンドが実行できるように必要に応じてインストールすればよいということになります。

尚、今回の場合 g++ を使うことは難しいでしょう。

厳密には g++ を使用することも可能ではあるのですが、 g++を使った場合、デフォルトではGNU製の「C++標準ライブラリ」が使われることになりますので 一旦それを避けるようにオプションを設定しなければなりません。

この GNU製の C++標準ライブラリの多くはヘッダファイルだけから構成されるのですが、 バイナリファイルとしての実体も存在し、libstdc++.soといった名前になっています。 libstdc++.so 自体のライセンスもまた glibc と同様にGPLとなりますから、 これを静的リンクすることができません。 またこれを動的リンクしたとしても結局 glibc の場合と同じ問題が起きることになります。
この libstdc++.so の中ではC言語標準ライブラリで定義された関数も使われています。 この場合、使用しているLinuxによって以下の二通りのケースが考えられます。

  • glibc がベースである通常のLinuxの場合

  • libstdc++.so をリンクすると、間接的に glibc とのリンクも発生することになります。 これでは勿論、元の木阿弥となり問題です。

  • Alpine LinuxのようにmuslがベースであるLinuxの場合

  • libstdc++.so をリンクしても glibc とのリンクは発生せず、musl とリンクされます (そのようにlibstdc++自体がソースコードからビルドされています)。 しかし glibc とのリンクが回避できたとしても、 上述したように libstdc++.so 自体のライセンスが glibc と同様にLGPLとなりますから、 結局 glibc の場合と問題の本質は同じです。

では替わりにどのC++標準ライブラリを使うかですが、 musl はあくまで「C言語標準ライブラリ」であり、これを「C++標準ライブラリ」として使うことはできません。 よって、現状最も有力な候補としては、LLVM のC++標準ライブラリになるかと思います。 この場合、C言語ライブラリとしては musl、 C++ライブラリとしては「LLVMのC++標準ライブラリ」(「LLVM付属のC言語ライブラリ」ではない)を使うという かなり込み入った状況になりますが、これについては後述します。

LLVM のC++標準ライブラリを使うなら、g++ではなく最初からllvm_clang全体をインストールし、 C言語標準ライブラリのみを musl に置き換える方が自然なやり方であると思われます。

「LLVM製の標準C言語ライブラリ」というのもあるようですが、 現段階では musl と比べ完成度が落ちるとのことです (実際、llvm_clang でもデフォルトではLLVM製のC言語標準ライブラリではなく glibc を使います)。 そのため「C言語標準ライブラリ」に限っては musl に置き換えます。

llvm_clang のインストールを避けたい状況である場合、g++を使いつつ LLVM のC++標準ライブラリだけをピンポイントでインストールする必要があるわけで、 システムによってはこれは簡単なこともあれば難しいこともあるかもしれません。

Moai の場合C言語しか使っていませんので、そもそも上記のような問題は関係ありません。 また znk_project では musl を内蔵しており、「sh make_world.sh install」の実行によって 内部で自動的にビルドされます。 そのため、Moai/Easter/Rarakuのビルドでは musl を別途インストールする必要はありません

厳密には znk_project 内の musl はオリジナルの musl と完全に同じではなく 若干手直ししたものになります。

参考: muslのインストール
繰り返しになりますが、Moaiのビルドでは musl を別途インストールする必要はありません。

とはいえ一応muslに敬意を表する意味で、 (Moaiのビルドは置いといて)muslを普通にインストールする方法についても、 参考までに解説しておきます。

Alpine Linuxなどシステムに最初から musl がインストールされている場合もありますが、 多くのLinuxの場合、これはシステムには存在しません。 インストールしたい場合は、ソースコードからビルドするのがよいでしょう。 尚、このビルドによって出来上がる musl ライブラリ自体は、 当然ながら glibc には依存しないものとなります。

まずはmuslの公式サイト から直接ソースコードを$HOME/Downloadsディレクトリ等へダウンロードするとよいでしょう。 バージョンは何でも構わないと思いますが、この記事ではVer1.2.6を使います。

「musl-1.2.6.tar.gz」と書かれたリンクを選ぶとよいでしょう。 以下にリンクも示しておきます。


ダウンロードが終わりましたら、ターミナルから以下のように実行し、まずこれを解凍しましょう($HOME/Downloadsディレクトリ直下にダウンロードしたものとします)。

cd $HOME/Downloads
mkdir musl_dir
mv musl-1.2.6.tar.xz musl_dir/
cd musl_dir
tar xvf musl-1.2.6.tar.xz
cd musl-1.2.6

この時点で $HOME/Downloads/musl_dir/musl-1.2.6 というディレクトリが出来上がり、 その配下にソースコード一式が置かれている状況になっているはずです。 尚、musl_dir というディレクトリは必須ではなく単に整理のために用意したディレクトリです。

後は INSTALL ファイルに書かれている内容の通りに実行するだけですが、 要点をまとめると、--prefixでインストールディレクトリを指定して configureを実行後、 make と make install を実行します。 例えば以下の通りです。

./configure --prefix=../install_dir
make
make install

これで完了です。 $HOME/Downloads/musl_dir/install_dir 配下に musl がインストールされたはずです。 $HOME/Downloads/musl_dir/install_dir/lib/libc.so などが存在するかを確認しましょう。

展開ディレクトリ musl-1.2.6 の方は削除して構いません。

Close


コンパイルおよびインストール


ビルドのための必須コマンドはすべて所定の位置にインストールされているものとして話をすすめます。

  1. ターミナルより、(znk_project内蔵の)muslをインストールする予定の絶対パスを環境変数 ZNK_MUSL_DIR で指定します。

  2. この場合の「muslのインストール先」とは、znk_project/musl内でビルドしたライブラリやヘッダファイルの格納先ということになります。 これは別にどこでも構いませんが、例えば、$HOME/musl_dir に musl をインストールしたい場合、 以下のように設定します。

    export ZNK_MUSL_DIR=$HOME/musl_dir
    

    念のために補足しますが、上記は最終的に「$ZNK_MUSL_DIR/lib/libc.so」などが存在する状態になるということです。

    また ZNK_MUSL_DIR に指定する値は必ず絶対パスとしてください。 これはディレクトリを変更した場合でも musl のある位置が狂わないようにするためです。 少しトリッキーですが以下のようにすることで「相対パス」風な指定で絶対パスを指定することもできます。

    export ZNK_MUSL_DIR=`pwd`/../install_dir/musl_dir
    

    「`pwd`」の部分が今自分がいるディレクトリの絶対パスとなります。 上記はそこから一つ上のinstall_dirの下のmusl_dirへmuslをインストールするということです。

    musl の場合でも使用する Makefile は Makefile_linux.mak となります。 Makefile_linux.mak は環境変数 ZNK_MUSL_DIR が定義されている場合、 musl を使用したビルドに自動的に切り替えるようになっています。
    Moaiをすべてビルドし終わった後は、環境変数ZNK_MUSL_DIRで指定した先のmusl(znk_project由来のmusl)は 役目を終えていますので邪魔であれば削除しても構いません。

  3. Moaiをインストールする予定のパスを環境変数 ZNK_INSTALL_DIR で指定します。

  4. export ZNK_INSTALL_DIR=$HOME/install_dir
    

    これについての詳細は既に述べた glibc 版でのインストールと同様です。 詳しくはそちらを参照してください。

  5. srcディレクトリへ移動し、以下のシェルスクリプトを実行します。

  6. cd znk_project/src
    sh ./make_world.sh install
    

    これについての詳細は既に述べた glibc 版でのインストールと同様です。 詳しくはそちらを参照してください。

通常のglibcを使ったビルドでは、linux-x86、linux-x64 といったディレクトリが作成され、 生成されるバイナリはそこに置かれますが、 musl を使ったビルドでは、このディレクトリが linux_musl-x86、linux_musl-x64 といった名前になります。 どちらも同じLinuxのバイナリではあるのですが、一般に glibc を使ったバイナリと musl を使ったバイナリは混ぜるべきではなく そのため、Moaiのビルドでもこれらを明確に区別しています。

以上でMoaiのコンパイルとインストールはすべて完了です。

動作確認


念のため、コンパイルおよびインストールされたmoaiが起動するか確認しておきましょう。 インストールディレクトリ配下のmoai-v$REL_VER ディレクトリへと移動し、moaiを起動します。 このときに実行するシェルスクリプトは moai.sh ではなくmoai_musl.shを使います。 例えばMoaiのバージョン番号が2.3の場合であれば以下のようになります。

cd $ZNK_INSTALL_DIR/moai-v2.3
sh moai_musl.sh

これについての詳細は既に述べた glibc 版でのインストールと同様です。 詳しくはそちらを参照してください。

ただし、musl を使ったMoaiでは、ディレクトリ$PLATFORM(バイナリファイルが配置されるディレクトリ)の 名前が linux_musl-x86、linux_musl-x64 などになっており、 Moaiがロードするプラグインが置かれたディレクトリ名もそのような名前を含むものになっています。
moai_musl.sh 内を見てもらえばわかりますが、 これは moai.sh を使って以下のように起動したのと同じことです。

cd $ZNK_INSTALL_DIR/moai-v2.3
LINUX_SFX=_musl sh moai.sh


llvm_clangとmuslでビルドする


ここまでは gcc と musl 使ってビルドしてきましたが、 llvm_clang と musl 使ってビルドすることもできます。 Moaiのビルドに限るならばそこまでする必要はありませんが、 特に C++ をビルドする場合、既に述べたように g++ ではなく llvm_clang++ を使う方が多分やりやすいです。 せっかくですのでそれについても説明しておきましょう。

この場合の手順は以下の通りです。

  1. ターミナルより、llvm_clangがインストールされているトップディレクトリのパスを環境変数 ZNK_LLVM_CLANG_DIR で指定します。

  2. 例えば、$HOME/Downloads/llvm_dir/v3.9/install_dir に llvm_clang がインストールされている場合、 以下のように設定します。

    export ZNK_LLVM_CLANG_DIR=$HOME/Downloads/llvm_dir/v3.9/install_dir
    

    念のために補足しますが、上記は「$ZNK_LLVM_CLANG_DIR/bin/clang」と入力すると llvm_clang が実行できる状態にしておくということです。

    llvm_clang の場合でも使用される Makefile は gcc の場合と同じくMakefile_linux.mak となります。 Makefile_linux.mak は環境変数 ZNK_LLVM_CLANG_DIR が定義されている場合、 llvm_clang を使用したビルドに自動的に切り替えるようになっています。

    そのため、この環境変数 ZNK_LLVM_CLANG_DIR の定義は必須です。 たとえ単に「clang」と打ってllvm_clangが実行できるようにPATHを設定していたとしてもです。 例えば llvm_clang が最初からシステムにインストールされている状態( /usr/bin/clang などが存在する状態)であった場合は、 「export ZNK_LLVM_CLANG_DIR=/usr」と設定することになります。

  3. ターミナルより、(znk_project内蔵の)muslをインストールする予定の絶対パスを環境変数 ZNK_MUSL_DIR で指定します。

  4. この場合の「muslのインストール先」とは、znk_project/musl内でビルドしたライブラリやヘッダファイルの格納先ということになります。 これは別にどこでも構いませんが、例えば、$HOME/musl_dir に musl をインストールしたい場合、 以下のように設定します。

    export ZNK_MUSL_DIR=$HOME/musl_dir
    

    念のために補足しますが、上記は最終的に「$ZNK_MUSL_DIR/lib/libc.so」などが存在する状態になるということです。

    musl の場合でも使用する Makefile は Makefile_linux.mak となります。 Makefile_linux.mak は環境変数 ZNK_MUSL_DIR が定義されている場合、 musl を使用したビルドに自動的に切り替えるようになっています。

  5. Moaiをインストールする予定のパスを環境変数 ZNK_INSTALL_DIR で指定します。

  6. export ZNK_INSTALL_DIR=$HOME/install_dir
    

    これについての詳細は既に述べた glibc 版でのインストールと同様です。 詳しくはそちらを参照してください。

  7. srcディレクトリへ移動し、以下のシェルスクリプトを実行します。

  8. cd znk_project/src
    sh ./make_world.sh install
    

    これについての詳細は既に述べた glibc 版でのインストールと同様です。 詳しくはそちらを参照してください。

以上でMoaiのコンパイルとインストールはすべて完了です。 動作確認等に関してはgccを使用した場合と同様ですので省略します。

muslで動的リンクを使ったバイナリを作る場合における注意点


Moaiをビルドするという目的だけならこれで終わりなのですが、 あなたがわざわざ musl を使うことを選んだということは、 このビルドしたバイナリを他のLinux PC(特に古いバージョンのLinux)でも動作させたいと考えているか、 あるいはご自分が作るアプリケーションに関してもmuslに対応させたく、 Moaiのビルドを参考にしているからかもしれません。 そこでここでは本筋から外れますが、muslについてもっと詳しく検証しましょう。

muslを使ったバイナリの場合、glibcを使ったバイナリにはない注意点があります。

まずmuslを使ったバイナリの場合、そのままですと単独で起動ができないことがあります。 例えばあなたのアプリケーション my_app が musl を使用しており、 かつ動的ライブラリ libc.so をリンクしてビルドしているとしましょう。 my_app.c の中身は以下のようなものとしましょう。

#include <stdio.h>
int main( int argc, char** argv )
{
	printf( "hello.\n" );
	return 0;
}

ご覧の通り、"hello." とだけ表示するこの上なくシンプルなものです。 musl の INSTALL に書かれた「Checking for Successful Installlation」によれば、 これをビルドするには以下のようにmusl-gccを実行するとのことです。

$ZNK_MUSL_DIR/bin/musl-gcc my_app.c -o my_app

ここで環境変数ZNK_MUSL_DIRにはmuslがインストールされているパスが指定されているものとしましょう。 特にエラーは出ません。 ls コマンドを実行すると my_app というファイルが出来上がっていることが確認できますが、 これが実行ファイルとなります。

尚、このように実行した場合、my_app は libc.so を動的リンクします。

さっそくこの my_app を起動させてみます。 次のようになるでしょう。

./my_app
bash: ./my_app: No such file or directory.

ご覧の通り「No such file or directory.」というエラーが出て起動に失敗します。 なんということでしょう! 「そのようなファイルはない」と言われてしまいました。

いや my_app は今作ったばかりやろがい!」と思わず言ってしまうでしょうが、 このエラーは(猛烈に誤解を伴う表記になっておりますが)「(あなたの)my_app ファイルがない」と言っているわけではありません。

次にあなたは「さては環境変数 LD_LIBRARY_PATH の設定が必要だな?」と思うかもしれません。 それを踏まえて実行すると以下のようになります。

export LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH
./my_app
bash: ./my_app: No such file or directory.

ご覧の通り残念ながら状況は変わりません。 このエラーの原因は環境変数 LD_LIBRARY_PATH ではありません。

この奇妙な状況を理解するにはLinux上で実行バイナリが起動する根本の仕組みを少し知る必要があります。 普段意識することはほとんどありませんが、 Linux上であなたの実行バイナリが起動する場合、 まずシステムに存在する動的リンカ(dynamic linker)(または動的ローダ(dynamic loader)と呼ばれる場合もあります)が実行され、 その動的リンカがあなたの実行バイナリを動的リンク(ロード)します。

ただしあなたの実行バイナリが動的ライブラリを一つ以上必要とする状況に限ります。 動的ライブラリを一つも必要としない場合は、逆に動的リンカを使わない起動が行われます。 これについては次項「muslで静的リンクを使ったバイナリを作る場合における注意点」において詳しく述べます。

ただしその動的リンカがどこに配置されているのかという情報はあなたの実行バイナリの内部、INTERPセクションに埋め込まれています (このINTERPはインタプリタ(interpreter)の略です)。 そのため、bashはまずあなたの実行バイナリのINTERPセクションを読み、 その値から実行すべき動的リンカのパスを得て、 その動的リンカを呼び出します。 muslを使わずに普通にビルドした場合(つまりglibcを使ってビルドした場合)、 通常のLinuxでは実行バイナリに埋め込まれるINTERPの値は「/lib64/ld-linux-x86-64.so.2」となっています。 この場合、bashは最終的に以下のようなコマンドを内部で実行します。

/lib64/ld-linux-x86-64.so.2 ./my_app

ls コマンドなどで調べると /lib64/ld-linux-x86-64.so.2 という動的ライブラリが確かに存在していることがわかりますが、 これが動的リンカの正体です。 これだけを見るとまるで sh や python などのインタプリタ型言語のように、 あなたの実行バイナリを引数として実行している形に見えます(そのためこのセクションの名前もINTERPとなっているのでしょう)。

それにしても動的ライブラリを単独で実行しているのは、普通に考えると奇妙に見えるかもしれませんが、 この /lib64/ld-linux-x86-64.so.2 というファイルは動的ライブラリと(単独実行可能な) 実行バイナリの両方の性質を兼ね備えている特殊なファイルとなります。 そのため、このようなことが可能なのです。

というよりそもそも動的ライブラリと実行バイナリはほとんど共通した構造をしています。 どちらも同じリンカで作られることを考えれば、これらの共通性はさほど不思議ではないかもしれません。

さて、先ほど musl を使って作った実行バイナリ my_app に話を戻しますが、 この my_app におけるINTERPセクションの値は実際どうなっているのでしょうか? これを調べるには readelf コマンドを使い、以下のように実行します(「-l」はハイフンとエルです)。

readelf -l ./my_app

表示されるメッセージの上の方に「INTERP」と書かれた項目があり、 そのすぐ下に以下のような表示があるはずです。

    [Requesting program interpreter: /lib/ld-musl-x86_64.so.1]

この「/lib/ld-musl-x86_64.so.1」があなたの my_app に埋め込まれているINTERPセクションの実際の値になります。 すなわち、あなたの実行ファイルのINTERPセクションではこれが動的リンカであると主張しているわけです。

muslという文字列が含まれていますが、 このようなファイルが本当にシステムの/libディレクトリに存在するのかは疑問です。 我々はソースから musl をビルドして、(我々がローカルに用意した)$ZNK_MUSL_DIR へそれを配置しただけであり、 sudo コマンド等によりmuslをシステムにインストールしたわけではないからです。 ls コマンドでこのファイルの存在を確かめてみます。

ls /lib/ld-musl-x86_64.so.1
ls: cannot access '/lib/ld-musl-x86_64.so.1': No such file or directory

思った通り「/lib/ld-musl-x86_64.so.1」というファイルはシステムには存在しないようです。 それにも関わらず、bash は以下のようなコマンドを内部で実行しようとしていたことになります。

/lib/ld-musl-x86_64.so.1 ./my_app

これで一番初めに出た「No such file or directory」というエラーの正体がわかりました。 あれは「(あなたの実行バイナリのINTERPセクションに書かれてある/lib/ld-musl-x86_64.so.1という名前の)ファイルは存在しない」 と言っていたわけです。

ただしあなたがもしAlpine Linuxを使っていた場合、 おそらくこのエラーは出ないでしょう。 Alpine Linux では、最初からシステムに「/lib/ld-musl-x86_64.so.1」が存在するからです。
musl の INSTALL に書かれた「Notes on Dynamic Linking」を見ると 以下のようになっています。

Notes on Dynamic Linking
------------------------

If dynamic linking is enabled, one file needs to be installed outside
of the installation prefix: /lib/ld-musl-$ARCH.so.1. This is the
dynamic linker. Its pathname is hard-coded into all dynamic-linked
programs, so for the sake of being able to share binaries between
systems, a consistent location should be used everywhere. 

上記で確かに「/lib/ld-musl-$ARCH.so.1」という形式のファイルが(muslにおいては)動的リンカであると記載されています。

さて、my_app の起動のために「ld-musl-x86_64.so.1」という名前の動的リンカが必要なことはわかりました。 しかしこのファイルは一体どこにあるのでしょう? muslのインストールディレクトリ内を探してもそのような名前のファイルは見あたりません。 INSTALL にもこれが作られる大元の場所についての記述は特にないように思われます。

しかしこれがもし、muslのソースのビルドによって作られるファイルならば muslのMakefileのどこかにそれに関する記述が存在するはずです。 というわけでまずは muslの展開ディレクトリ内にある Makefile を見てみましょう。 その中で以下のような記述があります。

LDSO_PATHNAME = $(syslibdir)/ld-musl-$(ARCH)$(SUBARCH).so.1

...途中省略...

$(DESTDIR)$(LDSO_PATHNAME): $(DESTDIR)$(libdir)/libc.so
	$(INSTALL) -D -l $(libdir)/libc.so $@ || true


変数 LDSO_PATHNAME が我々が探している動的リンカで間違いないでしょう。 これを一体どうやって作っているかですが、そのためのルールが最後の2行です。 ここで使われている$(INSTALL)コマンドはcopyコマンドのような働きをします。 その引数は「$(libdir)/libc.so $@」となっており、 ここでは「$(libdir)/libc.so」を「$@」へコピーするような処理が行われます。 この「$@」は、Makefileの文法で最終的に生成するファイルを意味する特殊変数で、 ここでは「$(DESTDIR)$(LDSO_PATHNAME)」を意味します。 要するにここで行われていることは、「libc.so」ファイルを「ld-musl-$(ARCH)$(SUBARCH).so.1」としてコピーしているということであり、 驚くべきことになんとこの二つのファイルは同一であるというのです。

「libc.so」は muslが作成したC言語の標準(動的)ライブラリです。 しかし同時にこれは動的リンカ「ld-musl-$(ARCH)$(SUBARCH).so.1」としても働く実行バイナリでもあることになります。 試しに libc.so を実行してみましょう。

$ZNK_MUSL_DIR/lib/libc.so
musl libc (x86_64)
Version 1.2.6
Dynamic Program Loader
Usage: $ZNK_MUSL_DIR/lib/libc.so [options] [--] pathname [args]

「動的リンカである」といったメッセージとその使い方が表示されました。 これで確証も得られました。 この libc.so を /lib/ld-musl-x86_64.so.1 へコピーすればよいということになります。

しかしながら /lib というのはシステムのディレクトリです。 ここにコピーするには root 権限が必要です。 またそれであなたのPCでは my_app が起動できるようになったとしても、 別のPC(例えばあなたのアプリを使うユーザのPC)には「/lib/ld-musl-x86_64.so.1」は多分入っていないわけですから、 そこでまた同じエラーが生じるでしょう。 そのPCでも「/lib/ld-musl-x86_64.so.1」を作らなければならないことになります。

muslを導入したそもそもの動機は、特に何も準備することなく 「ほとんどどんなLinux上でも起動できるバイナリ」を作ることでした。 しかしこれでは逆に「ほとんどどんなLinux上でも起動できないバイナリ」ではありませんか? これは不味いです。 なんとかして「/lib/ld-musl-x86_64.so.1」を作らずとも 起動する方法を考えねばなりません。

ここで今一度 bash が最終的に以下のようなコマンドを実行しようとして エラーになっていることを思い出しましょう。

/lib/ld-musl-x86_64.so.1 ./my_app

また、libc.so は/lib/ld-musl-x86_64.so.1と同一のファイルであり、 これ自体が単独のインタプリタのように実行可能なバイナリでもあります。 ならば、上記の「/lib/ld-musl-x86_64.so.1」の部分を 「$ZNK_INSTALL_DIR/lib/libc.so」に置き換えて実行可能ではないでしょうか? 試しに実行してみます。

$ZNK_INSTALL_DIR/lib/libc.so ./my_app
hello.

うまくいきました。 もう一押ししましょう。 この libc.so を my_app と同じディレクトリにコピーし、 次のように実行します。

./libc.so ./my_app
hello.

これもうまくいきました。 つまり my_app を他のPCで実行する場合、my_app といっしょにこのlibc.so を配布すればよいことになります。 しかもこの libc.so は my_app が依存する動的ライブラリ(ランタイムライブラリ)でもありますから、 どのみち my_app に付属させる必要があるファイルです。

これで起動に関する重大な懸念事項は解決しました。 しかしこの方法で起動する場合、常に「./libc.so ./my_app」というまるでインタプリタ言語のような形式で my_appを起動させなければなりません。my_app はネイティブバイナリなのですから、 やはり単に「my_app」と入力しただけで起動できるようにしたいものです。

ここで次のような方法を思いつくかもしれません。 my_app の INTERPセクションの値を「libc.so」にしてビルドすることができれば、 あとは「./my_app」を実行しただけで自動的に「libc.so ./my_app」が実行されるのではないか?と。

実はリンカに「--dynamic-linker」オプションを指定することによって、 この INTERP の値をこちらで任意に指定することが可能です。 例えばリンカに「--dynamic-linker libc.so」と指定した場合、生成される実行バイナリのINTERPの値は「libc.so」になります。 gcc を介してリンクする場合、以下のように「-Wl」オプションを使用すればよいでしょう。

$ZNK_INSTALL_DIR/bin/musl-gcc -c my_app.c
$ZNK_INSTALL_DIR/bin/musl-gcc -o my_app my_app.o -Wl,--dynamic-linker,libc.so

生成された my_app に対し、readelf コマンドを実行してみましょう。

readelf -l ./my_app

以下のように表示されるはずです。

    [Requesting program interpreter: libc.so]

これで単に「./my_app」と実行しただけで、自動的に「libc.so」が動的リンカとして実行されるはずです。 実際に「./my_app」を実行してみます。

./my_app
hello.

うまくいきました!

これで万事解決…と言いたいところですが、 cd コマンドで一つ上のディレクトリへ移動し、今度はその位置から再びこれを実行してください。

cd ..
my_dir/my_app
bash: my_dir/my_app: No such file or directory.

また同じエラーが発生しました。 しかしよく考えるとこれは当然で、 INTERPの値は単に「libc.so」ですからカレントディレクトリの「libc.so」を実行することになります。 今、一つ上のディレクトリに移動したため、カレントディレクトリには libc.so は存在しないのですから、 bashが内部で実行する「libc.so my_dir/my_app」も当然エラーとなるわけです。

ここであなたは次のように考えるかもしれません。 INTERPに設定されている値は単に「libc.so」であるから、 環境変数PATHでこれが存在するディレクトリのパスを指定してやれば、 別のディレクトリに移動してもこれが実行可能ではないか?と。 確かに普通にbashのルールで考えればそれでうまくいきそうです。 試しに以下のように環境変数PATHを設定して実行してみます。

PATH=my_dir:$PATH my_dir/my_app
bash: my_dir/my_app: No such file or directory.

残念ながらこれはうまくいきません。 どうやら bash は INTERPセクションから読んだ値に対しては、 環境変数PATHの適用などしないようです。

その他、INTERPそのものに「$ZNK_MUSL_DIR/lib/libc.so」といった環境変数付きの文字列を埋め込む方法を思いつくかもしれませんが、 これもうまくいきません。

INTERPに「/lib64/ld-linux-x86-64.so.2」を無理やり埋め込むとどうなるのでしょうか? musl-gcc がわざわざこの指定を避けているということは、 これだと問題があるからなのでしょうが、一応どうなるかを確認しておきましょう。 次のように実行します。

$ZNK_INSTALL_DIR/bin/musl-gcc -c my_app.c
$ZNK_INSTALL_DIR/bin/musl-gcc -o my_app my_app.o -Wl,--dynamic-linker,/lib64/ld-linux-x86-64.so.2

ビルドまではうまくいきます。 「readelf -l ./my_app」でINTERPセクションが「/lib64/ld-linux-x86-64.so.2」になっていることも確認できます。 問題は「./my_app」を実行してどうなるかですね。

./my_app
./my_app: error while loading shared libraries: /usr/lib/libc.so: invalid ELF header

「No such file or directory」は出ませんので、 動的リンカとして「/lib64/ld-linux-x86-64.so.2」が実行できてはいるようです。 しかしおそらく「/lib64/ld-linux-x86-64.so.2」の実行中、 問題が発生してエラーとなっています。

しかもよく見ると、システムデフォルトのCライブラリ(glibc)である「/usr/lib/libc.so」をロードしようとしています。 動的リンカ「/lib64/ld-linux-x86-64.so.2」を使った場合、 問答無用でglibcがロードされるということなのでしょう(しかも整合性がとれず結局失敗します)。 そのため、musl-gcc はこれを動的リンカとして指定することを避けていたということなのでしょう。
INTERPを設定する方法についてはこのあたりが限界と思われます。 この値を変更するにしてももうあまり弄らず単なる「libc.so」という値にしておく方がよさそうです。

任意のディレクトリで「my_app」を(それ単独の入力で)起動可能にするためには INTERPの値を絶対パスで(しかも環境変数なしで)設定するしかありませんが、 そのようにすると結局他のPCでは起動できないという最初の問題に逆戻りします。

では最終的に我々の開発したRarakuで採用している方法を説明しましょう。 Rarakuでは(musl版に限りますが) まず実行バイナリを(通常、musl版以外のLinuxの場合はrarakuという名前で作るところを)raraku.binという名前で作成します。 また、以下のようなシェルスクリプトを用意し、こちらの名前はrarakuとします。

#!/bin/sh
# For musl dynamic load binary.
# For example, rename this file to your_app, and run libc.so(musl dynamic linker) with your_app.bin(native binary)
# Regarding the problem of "$0" when using source command, there is a way to address it using SELF_PATH="${BASH_SOURCE:-$0}",
# but this environment variable requires bash 3.0 or later.
SELF_PATH="$0"
SELF_NAME=`basename $SELF_PATH`
if test "$THIS_DIR" = "" ; then 
	THIS_DIR=`dirname $SELF_PATH`
fi
BIN_FILE=$THIS_DIR/$SELF_NAME.bin
if test "$DYNAMIC_LINKER" = "" ; then 
	if test "$ZNK_MUSL_DIR" = "" ; then 
		DYNAMIC_LINKER=$THIS_DIR/libc.so
	else
		DYNAMIC_LINKER=$ZNK_MUSL_DIR/lib/libc.so
	fi
fi
$DYNAMIC_LINKER $BIN_FILE "$@"

上記で"$0"についてはこのシェルスクリプト自身のフルパスが格納されている変数になります。 basenameコマンドでこのシェルスクリプト自身の名前が取得でき、SELF_NAMEにその値が格納されます(今回の場合rarakuとなります)。 またdirnameコマンドでこのシェルスクリプト自身が存在するディレクトリのパスが取得でき、THIS_DIRにその値が格納されます。 あとは動的リンカ DYNAMIC_LINKER の値を適当に決め、それをあたかもインタプリタ言語のように実行する形になります(結局この方法が一番確実です)。 またその引数として実行バイナリの本体 $BIN_FILE も指定します。 "$@" はこのシェルスクリプト自体に与えられた全引数となります。

これによりrarakuファイルが存在するディレクトリからの実行は勿論のこと、 任意のディレクトリにおいてもこれを(外からは)単独で実行可能になります。

ただしここまでやっても微妙に問題があります。 まずこのようにするとシェルスクリプトという皮を一枚被せているため、 デバッグする際、デバッガ(gdb)での追跡が面倒なことになるということです。 また、これの呼び出しを含むシェルスクリプトを bash の source コマンドで実行する場合、 "$0"の値が期待した通りのものになりません(この値がsource コマンドを実行したトップのシェルスクリプトのパスとなってしまいます)。

このsource コマンドに関する問題に対処するため、BASH_SOURCE という環境変数を使う方法もあるようですが、 この環境変数は bash 3.0 からのサポートとなります。 このバージョンを古いと見るか新しいと見るかですが、 例えば OpenDarwin8(darwin8相当)では bashのバージョンは2.0であり、この方法は使えません。

もっともDarwinは当然ながらLinuxではないため、muslとも直接関係ありませんが、 (Linuxではないにしろ)Moaiが明確に対応しようとしている環境におけるbashのバージョンが2.0であるという事実は、 一つの考慮すべき指標になります。 また現在の musl(Ver1.2.6)はLinux Kernel Ver.2.6以上の環境をサポートします。 このKernel Ver2.6 がリリースされたのが2003年あたりであり、 逆に2003年あたりで出回っていたbashのバージョンはおおよそ2.0あたりです。 これらを考え合わせると、「bash が3.0 以上」というのは制限が強すぎると我々は見ます。

muslを導入したそもそもの動機は、ほとんどどんなバージョンのLinuxでも動作させることであったはずです。 特に新しいバージョンのLinuxでビルドしたバイナリが古いバージョンのLinuxで動作しないことが 問題であったわけで、それなのにbash のバージョン程度のことで古いバージョンのLinuxを足切りしては結局意味がなくなります。

sourceコマンドでこれを使う場合は、一旦変数THIS_DIRにこのシェルスクリプトのパスを設定し、 その上でこのスクリプトを実行します。 この変数が予め設定されている場合はその値を参照しますし、 されていない場合はある程度自動的にTHIS_DIRの値を取得して使う形となります。

muslで静的リンクを使ったバイナリを作る場合における注意点


前項ではmuslの動的ライブラリlibc.soを動的リンクする方法とその注意点を見ました。 今回はmuslの静的ライブラリlibc.aを静的リンクする方法とその注意点を見ます。

musl の INSTALL には静的リンクでビルドする方法については明記されていないように見えます。 前項で見た通り、musl-gcc をそのまま実行するとlibc.soと動的リンクされますが、 ひとまずなぜこうなるのかを確認しましょう。

このことは「-v」オプションを指定して「../install/bin/musl-gcc」を実行すると確認できます。 表示されるコマンドラインオプションに「-nostdlib」「-L$ZNK_INSTALL_DIR/lib」「-lc」が含まれているはずです。 「-nostdlib」はgccデフォルトのCライブラリ(glibc)のリンクを行わないことを意味します。 「-L$ZNK_INSTALL_DIR/lib」は次に続く「-l」オプションで指定されるライブラリの検索パスです。 「-lc」は「libc.so」が存在する場合はそれを動的リンクし、存在しない場合は「libc.a」を静的リンクすることを指示するオプションです。 今回は$ZNK_INSTALL_DIR/lib内にlibc.soが存在しますので、そちらが優先されてこれが動的リンクされるというわけです。

ではこれを逆にする、すなわち静的ライブラリlibc.aとの静的リンクを優先させるにはどうすればよいのでしょうか? 結論から言えば「-static」オプションを指定し、次のように実行します。

musl-gcc -nostdlib -o my_app my_app.o -static

gccの「-static」オプションをご存知の場合、 上記を見て「それはそうだろう」という感想を持つかもしれませんし、 逆に「musl-gccという(gccと似てはいそうだが違う)ツールに(gccと同じように) 『-static』オプションを与えて本当によいのか?」 という感想を持つかもしれません。 これについては次の項「musl-gcc内部で行われていることを完全掌握する」で詳しく検証します。
特にエラーは出ません。 ls コマンドを実行すると my_app というファイルが出来上がっていることが確認できますが、 これが実行ファイルとなります。

さっそくこの my_app を起動させてみます。 次のようになるでしょう。

./my_app
hello.

ご覧の通り「hello.」が表示され、うまくいっています。 なんということでしょう! 静的リンクの場合、なんのトラブルもなく簡単に成功してしまいました。

だったら最初からこっちを先に教えてくれ!」と言いたくなるかもしれませんが、 実はmuslの静的リンクには重要な注意事項があります。

問題は dlopen という C言語の標準関数にあります。 この関数は so ライブラリを実行時に動的ロードするためのものです。

動的リンクとの違いにご注意ください。 動的リンクとは、一般に起動時に動的リンカが自動的にあなたの my_app を動的ライブラリとリンクさせる機能のことです。 一方、動的ロードとはあなたの my_app が実行中に自力で dlopen を呼び出し、外部の動的ライブラリとリンクさせる機能のことです。 例えばプラグインなどは動的ロードに該当します。 ただしこれらの用語は文脈によっては混同して使用される場合もあります。

結論から言うとmuslを静的リンクした場合、dlopen関数が使えません。 特にMoaiの場合はpluginがあり、Rarakuの場合はextens機能によりネイティブライブラリを動的ロードしますから、 このdlopen関数が使えないという状態は致命的な問題となります。

またたとえ直接 plugin を使わないアプリケーションであったとしても 使用ライブラリの内部で dlopen を使っており、間接的に影響を受ける可能性もあります。 一方、muslを動的リンクした場合、dlopen関数が使えるのです。 つまりmuslを静的リンクするか動的リンクするかで、dlopenの挙動が変わる(というより前者の場合そもそも機能しない)のです。

しかしそんなおかしなことが本当にあるのでしょうか? 実際にdlopenを使っているソースコードを作り、 静的ライブラリと動的ライブラリのそれぞれをリンクしてみて確認することもできますが、 この記事ではmuslのソースコードからこれを検証します。

musl においても当然この dlopen 関数の実体が定義されているはずです。 この dlopen が定義されているソースファイルはどこでしょうか? ソースディレクトリ内の src/ldso/dlopen.c にそれらしきものがあります。 この中身を見てみましょう。

#include <dlfcn.h>
#include "dynlink.h"

static void *stub_dlopen(const char *file, int mode)
{
	__dl_seterr("Dynamic loading not supported");
	return 0;
}

weak_alias(stub_dlopen, dlopen);

なんとたったこれだけです! いえ、そんなはずはないのです。 dlopenは非常に複雑な処理を必要とする関数で、 その定義がこれだけで済むはずがないのです。 まして「__dl_seterr("Dynamic loading not supported");」など… んなアホなッ!「動的ロードがサポートされていません」と書かれてあるではないですか!

一旦落ち着きましょう。

よく見ると最後に奇妙な記述があります(少なくともC言語をご存知な方は奇妙に見えるでしょう)。 このweak_aliasとは何でしょうか? まるで関数呼び出しのように記述されていますが、 そこはグローバルスコープであるため、本来関数呼び出しが出来る位置ではありません。 ということはこれは何かのマクロか、さもなくばコンパイラの拡張機能に違いありません。 こいつは何者なのかを調べます。 muslのソースディレクトリのトップで「grep -r weak_alias * | grep define」などと実行すると src/include/features.h ファイルが浮かびあがります。 この中身は以下の通りです。

#ifndef FEATURES_H
#define FEATURES_H

#include "../../include/features.h"

#define weak __attribute__((__weak__))
#define hidden __attribute__((__visibility__("hidden")))
#define weak_alias(old, new) \
	extern __typeof(old) new __attribute__((__weak__, __alias__(#old)))

#endif

やはりありました。 weak_aliasはマクロです。 さらにその定義において __attribute__ キーワードが使われておりますが、 これは gcc の拡張機能です。 やはり「普通ではないこと」が行われているとわかります。 この記述は new で指定したシンボルが存在しない場合はoldを使用するといった意味になります。 つまり元のdlopen.cでの記述「weak_alias(stub_dlopen, dlopen);」は 「dlopen の定義が存在しなければstub_dlopenを使用する」ということです。

これはある意味 dlopen のデフォルトとしてstub_dlopenが用意されているということでもあります。

しかし「dlopen の定義が存在しなければ」とはどういうことでしょうか? 「dlopen の定義が存在する」場合と 「dlopen の定義が存在しない」場合の二通りがあるとでも言うのでしょうか? またもしそうだとして、どのような場合に前者と後者が起こるのでしょうか?

まあ今回の場合、ゴールはわかっているわけで その「二通り」とは、 libc.so と libc.a のことであろうと予想できます。 実際、musl のソースディレクトリにある Makefile に以下のような記述があります。

ALL_OBJS = $(addprefix obj/, $(filter-out $(REPLACED_OBJS), $(sort $(BASE_OBJS) $(ARCH_OBJS))))

LIBC_OBJS = $(filter obj/src/%,$(ALL_OBJS)) $(filter obj/compat/%,$(ALL_OBJS))
LDSO_OBJS = $(filter obj/ldso/%,$(ALL_OBJS:%.o=%.lo))
CRT_OBJS = $(filter obj/crt/%,$(ALL_OBJS))

AOBJS = $(LIBC_OBJS)
LOBJS = $(LIBC_OBJS:.o=.lo)

...途中略...

lib/libc.so: $(LOBJS) $(LDSO_OBJS)
	$(CC) $(CFLAGS_ALL) $(LDFLAGS_ALL) -nostdlib -shared \
	-Wl,-soname,libc.so \
	-Wl,-e,_dlstart -o $@ $(LOBJS) $(LDSO_OBJS) $(LIBCC)

lib/libc.a: $(AOBJS)
	rm -f $@
	$(AR) rc $@ $(AOBJS)
	$(RANLIB) $@

最後のlib/libc.soとlib/libc.aを作っているルールから逆算して見ましょう。

lib/libc.so を作るには LOBJS と LDSO_OBJS が必要です。 一方、lib/libc.a を作るには AOBJS だけが必要です。

少し上に戻ると、LOBJS と AOBJS は どちらも要するに LIBC_OBJS が元になっており、基本的に同じものです ( LOBJSの方の拡張子が.loになっていますが、この違いはmuslの場合 -fPIC オプションを付けてコンパイルされたオブジェクトであるか否かの違いとなっており、 いずれにせよここでは気にする必要はありません)。

ということは libc.so の方だけ LDSO_OBJS なるオブジェクトが追加でリンクされていることになります。 これは如実に怪しいですね。

LDSO_OBJS については上の方に書かれており、ALL_OBJS(すべてのオブジェクトファイル)のうち obj/ldso に含まれるオブジェクトファイルの集合です。 一方 LIBC_OBJS については、ALL_OBJS のうち obj/src と obj/compat に含まれるオブジェクトファイルの集合ですので、 これらに共通部分はないことになります。

ではこの obj/ldso に含まれるオブジェクトファイルのソースコードはどこでしょうか? これについてはMakefile内に直接書かれているように見えませんが、 実は以下のサフィックスルール(GNU Makefileでは正確にはパターンルールと呼ばれます)がこの位置を暗に示しています。

obj/%.o: $(srcdir)/%.c $(GENH) $(IMPH)
$(CC_CMD)

サフィックスルール内の「%」という記号はディレクトリを再帰的に辿ったパターンマッチも行います。 例えば obj/ldso/xxx.o のソースコードは、この場合 $(srcdir)/ldso/xxx.c になります。 Makefile の一番初めには「srcdir = .」と書かれてあり、 結局、ディレクトリ obj/ldso 内のソースコードは ディレクトリ ./ldso 内にあるということになります。

ディレクトリ ./ldso 内には、dlstart.c と dynlink.c があります。 dynlink.c を見ると、2099行目あたりにおいて、dlopen 関数の(まともな)定義が存在することが確認できます。

これでわかりました。 結局、lib/libc.so の方にのみ dlopen 関数の定義が存在し、こちらはそれが呼ばれて普通に機能します。 一方、lib/libc.a にはその定義が存在しないため、weak_alias によって stub_dlopen が dlopen の替わりを担っていたのです。

参考: 静的リンクの場合、実行バイナリのINTERPセクションはどうなっておるのか?
静的リンクした my_app に対して「readelf -l my_app」を実行しますと、 INTERPセクションが存在しないことがわかります。 INTERP セクションが存在しない場合、bash は動的リンカによる起動は行わず、 あなたのバイナリを直接起動しようとします。 そのため「動的リンカが存在しない」といったエラーがそもそも発生しないことになります。

しかしこれがうまくいく理由は muslのlibc.aを静的リンクしたからというより、 my_app がたまたま動的ライブラリを全く使っていないからです。

例えばあなたが独自に作った動的ライブラリ libMyUtil.so を動的リンクしたい場合、 (たとえmuslに関してはlibc.aを静的リンクしていたとしても)動的リンカによる my_app の起動を行わなければなりません (まず動的リンカが起動されなければ、(対象となる動的ライブラリがなんであれ)動的リンクそのものができないからです)。 そしてこの場合、my_app のINTERP セクションを作る必要があり、 結局 libc.so を動的リンクした時と同じ問題に直面します。

ただし libc.a を静的リンクしつつ、あなたの動的ライブラリ libMyUtil.so を動的リンクさせるのは、 場合によってはちょっと複雑な指定を必要とするかもしれません。 例えば、libMyUtil.so の他に libMyUtil.a が存在した場合、 単に「-static」と指定しただけでは、libc.a だけでなく libMyUtil.a まで静的リンクされることになります。 その意味では、このような場合、動的ライブラリのファイル名 libMyUtil.so を直接指定する方がよいかもしれません。

尚、「-v」オプションを指定して「../install/bin/musl-gcc」を実行すると確認できますが、 「-static」オプションを指定した場合でも musl-gcc は内部で「-dynamic-linker」オプションにより INTERP セクションを作成しようとします。 ただし my_app が依存する動的ライブラリが全くない場合は単にその指定が無視され、 my_app 内にINTERPセクションが作られないことになります。

Close


musl-gcc内部で行われていることを完全掌握する


最終的にmusl-gccを使わず gcc 単独でmuslを使用したり、llvm_clangでmuslを使用するためには musl-gcc によるビルドから卒業する必要があります。 ここでは「../install/bin/musl-gcc」が中で何をやっているのか精査しましょう。 そのことでmusl-gccを使わないビルドに関する世界の全貌も見えてくるはずです。

musl-gcc の正体は、単に gcc を呼び出しているだけのシェルスクリプトです(このようなものをラッパー(wrapper)と呼びます)。 その中身は以下のようになっています。

#!/bin/sh
exec "${REALGCC:-gcc}" "$@" -specs "$ZNK_INSTALL_DIR/lib/musl-gcc.specs"

想像以上にシンプルです。

まず「"${REALGCC:-gcc}"」の部分は要するに「gcc」です。 「"$@"」の部分はあなたが与えたコマンドライン引数全てが格納されます。 「-specs "$ZNK_INSTALL_DIR/lib/musl-gcc.specs"」の部分は musl-gcc が独自に付加するコマンドライン引数についての情報です。 よって「$ZNK_INSTALL_DIR/lib/musl-gcc.specs」に書かれている内容を調べればよいことになります。

この「$ZNK_INSTALL_DIR/lib/musl-gcc.specs"」の中身は以下のようになっています。

%rename cpp_options old_cpp_options

*cpp_options:
-nostdinc -isystem $incdir -isystem include%s %(old_cpp_options)

*cc1:
%(cc1_cpu) -nostdinc -isystem $incdir -isystem include%s

*link_libgcc:
-L$libdir -L .%s

*libgcc:
libgcc.a%s %:if-exists(libgcc_eh.a%s)

*startfile:
%{!shared: $libdir/Scrt1.o} $libdir/crti.o crtbeginS.o%s

*endfile:
crtendS.o%s $libdir/crtn.o

*link:
-dynamic-linker $ldso -nostdlib %{shared:-shared} %{static:-static} %{rdynamic:-export-dynamic}

*esp_link:


*esp_options:


*esp_cpp_options:

実際にはこれは「$ZNK_INSTALL_DIR/lib/musl-gcc.specs"」そのものではなく、 一部を($で始まる)変数として抽象化して書いてあります。 例えば「$incdir」は「$ZNK_INSTALL_DIR/include」、「$libdir」は「$ZNK_INSTALL_DIR/lib」、 「$ldso」は「/lib/ld-musl-x86_64.so.1」といった文字列が入ります。

musl-gcc.specs はmuslソースディレクトリにあるシェルスクリプト tools/musl-gcc.specs.sh によって生成されるようですが、 上記はそのシェルスクリプトから引用したものです。

このファイル自体はgccのspec fileと呼ばれるもので、 記法についてはGNUのサイトhttps://gcc.gnu.org/onlinedocs/gcc-4.5.2/gcc/Spec-Files.html に解説があります。 とはいえ全部読むのは大変ですので、必要な箇所だけ掻い摘んで説明しましょう。

まず「musl-gcc.specs"」の最初は以下のようになっています。

%rename cpp_options old_cpp_options


この「%rename」の意味ですが、GNUのサイトには以下のような記述があります。

%rename old_name new_name
	Rename the spec string old_name to new_name. 

spec stringの名前を変える(Rename)といったようなことが書かれてあります。 このspec stringとは、gcc のコマンドラインオプションの値を決定する規則(Makefileで言うところのルール)のようなものです。 cpp_options という spec string は既に定義されており、 その名前をわざわざ「old_cpp_options」に変更しています。 ということはおそらく次に新しい「cpp_options」を定義し、しかも古い「cpp_options」も後で使うということでしょう。 次の行を見てみます。

*cpp_options:
-nostdinc -isystem $incdir -isystem include%s %(old_cpp_options)

予想通り「cpp_options」の定義「*cpp_options:」が現れました。 さらに空行を挟まずして「-nostdinc -isystem $incdir -isystem include%s %(old_cpp_options)」と続いています。 「-nostdinc」「-isystem」はgccに与えるオプション文字列そのものです。 「-nostdinc」はgccデフォルトのCヘッダ(glibcのヘッダ)のインクルードを行わないことを意味します。 「-isystem」はインクルードフラグに関するオプションで、 例えばC言語のヘッダファイル「stdio.h」をインクルードする場合の検索ディレクトリとして、 デフォルト(/usr/include)を使わず、替わりに後ろで指定した文字列のディレクトリを使うといったようなことです。

後ろの方に「include%s」といった不思議な記述が見られますが、これは何でしょうか? GNUのサイトには以下のような記述があります。

%s
	Current argument is the name of a library or startup file of some sort.
	Search for that file in a standard list of directories and substitute the full name found.
	The current working directory is included in the list of directories scanned.

ここで「Current argument」とは「%s」の直前に付いている文字列のことだと思われます。 例えば「include%s」の場合、「include」が「Current argument」ということです。 ただし「include」という文字列は「the name of a library」でも「startup file of some sort」でもなく ただのディレクトリであると思われます。 しかしながらその後に「standard list of directoriesの範囲においてこの文字列で検索を行い、フルネームを得る」といったようなことが書かれていることから、 この「include」で検索を行い、フルパス(ただし絶対パスとは限りません)を得ているものと思われます。 例えばここで言う「standard list of directories」に「$ZNK_INSTALL_DIR」が含まれる場合、 この検索によって「$ZNK_INSTALL_DIR/include」といったパスが導出できるということなのでしょう。

最後の位置にある「%(old_cpp_options)」は最初にRenameした「オリジナルのcpp_options」です。 これでここで行われていることがはっきりしました。 要するに「オリジナルのcpp_options」の最初の位置に「-nostdinc -isystem $incdir -isystem include%s」というオプションを挿入しているのです。

cpp_optionsという名前からその役割を推理しましょう。 「cpp」とはC++のことです。 よってこれはC++をコンパイルする際のオプション、 すなわちC++からオブジェクトファイルに変換する場合にgccに与えるべきオプションであると推察できます。 確かにmuslでコンパイルする場合、glibcのヘッダではなくmuslのヘッダをインクルードしなければなりませんから、 この部分についてのオプションも変更する必要があるわけです。

クロスコンパイルについてご存知な方は、ここで行われている処理はそれとよく似ていると思われたかもしれません。 それはその通りで、クロスコンパイルにおいても ホストシステムのライブラリではなく ターゲットシステムのライブラリをインクルード/リンクしなければならないため、 同じような処理が行われることになります。

次の行を見てみます。

*cc1:
%(cc1_cpu) -nostdinc -isystem $incdir -isystem include%s

「cc1」とはC言語をコンパイルするコンパイラの実体です。 gcc は内部でこれを呼び出します(実際にはCPUによってどのコンパイラを使うかが異なるため、 %(cc1_cpu)というspec stringでさらに実体をラップしているものと思われます)。 コンパイルオプションは先ほど見たC++でのオプション「-nostdinc -isystem $incdir -isystem include%s」と全くいっしょです。

次の行からいよいよリンクに関するspec stringとなります。 一気に見てみましょう。

*link_libgcc:
-L$libdir -L .%s

*libgcc:
libgcc.a%s %:if-exists(libgcc_eh.a%s)

*startfile:
%{!shared: $libdir/Scrt1.o} $libdir/crti.o crtbeginS.o%s

*endfile:
crtendS.o%s $libdir/crtn.o

*link:
-dynamic-linker $ldso -nostdlib %{shared:-shared} %{static:-static} %{rdynamic:-export-dynamic}

「*link_libgcc:」ではリンクにおける検索オプション「-L」を指定しているものと思われます。 「.%s」はやや奇妙な記述ですが、カレントディレクトリそのもののことだと考えてよいでしょう。

「*libgcc:」ではlibgcc(gccが独自に付加するライブラリ)に関する指定を行っています。 「libgcc.a%s」ではlibgcc.aのフルパスを得ています。 「%:if-exists(libgcc_eh.a%s)」についてはGNUのサイトに以下のような記述があります。

%:function(args)
	Call the named function function, passing it args.
	args is first processed as a nested spec string, then split into an argument vector in the usual fashion.
	The function returns a string which is processed as if it had appeared literally as part of the current spec.
    The following built-in spec functions are provided:

	if-exists
		The if-exists spec function takes one argument, an absolute pathname to a file.
		If the file exists, if-exists returns the pathname.
		Here is a small example of its usage:

			*startfile:
			crt0%O%s %:if-exists(crti%O%s) crtbegin%O%s

長いですが要するに「%:if-exists(X)」に形においては、 Xが存在する場合はこの部分が「Xの絶対パス」に展開されるということです。 Xが存在しない場合については明記されておりませんが、おそらくここは空になるのでしょう (さもなくば最終的にリンカでエラーになるからです)。 よって「%:if-exists(libgcc_eh.a%s)」の部分は、もしも「libgcc_eh.a%s)」が 存在する場合は、この部分に「libgcc_eh.a」の絶対パスが展開されるということになります。

「*startfile:」では、gccが付加するstart fileと呼ばれるオブジェクトファイルに関する指定です。 「*endfile:」も同様で、gccが付加するend fileと呼ばれるオブジェクトファイルに関する指定です。 ただし「$libdir/Scrt1.o」については「-shared」オプションが与えられていない場合のみ指定する必要があり、 それを行っているのが「%{!shared: $libdir/Scrt1.o}」となります。

crtbeginS.o と crtendS.o についてはmuslでビルドされたオブジェクトファイルではなく gccに付属するオブジェクトファイルであると思われます。 実際、muslのインストールディレクトリにはこれらのファイルがない一方で、 gccのインストールディレクトリ(/lib64/gcc/x86_64-pc-linux-gnu/11.2.0 など)には これらのファイルがあることが確認できます。
「-shared」オプションは動的ライブラリを作る場合に指定するgccのオプションです。 ということは「$libdir/Scrt1.o」については動的ライブラリを除くすべての場合において 指定しなければならないオブジェクトファイルということになります。

最後に「*link:」があります。 長かったですが、ここが一番重要と思われます。 「-dynamic-linker $ldso」は見覚えがあるオプションです。 既に説明した通り、これにより動的リンカを明示的に指定できます。 gcc-muslでコンパイルするとデフォルトではINTERPセクションに「/lib/ld-musl-x86_64.so.1」といった文字列が入るのは、 ここでこれが明示的に指定されていたからですね。 「-nostdlib」も既に説明した通りで、gccデフォルトのCライブラリ(glibc)のリンクを行わないことを意味します。 「%{shared:-shared}」についてはGNUのサイトに以下のような記述があります。

%{S:X}
	Substitutes X, if the `-S' switch was given to GCC. 

もしもgccに「-S」というオプションが与えらた場合は、(「:」の後ろに続く)Xという文字列で置き換える といったようなことが書かれてあります。 よって例えば「%{shared:-shared}」の場合、「-shared」というオプションが与えられた場合、 それを「-shared」という文字列で置き換えるということになります。

結局「-shared」から「-shared」にしているわけで何も変更が加えられておらず、別に必要ない記述のように思えますが、 「-shared」オプションに関してはそのまま渡すという事実そのものを明記する意図で記述しているのかもしれません。 「-static」オプションに関しても同様です。

また例えば「%{rdynamic:-export-dynamic}」の場合、「-rdynamic」というオプションが与えられた場合、 それを「-export-dynamic」という文字列で置き換えるということになります。 こちらの方がわかりやすい例かもしれません。

さてこれで我々はgcc-muslが内部でやっていることを完全掌握しました。 わかったことを端的にまとめますと gcc-musl は(--dynamic-linkerでの指定を除けば)さほど余計なことはしておらず、 最低限glibcとの関連を切った上でmuslに関するインクルードフラグ、リンクフラグ、必要なオブジェクトファイルなどを付加し、 あとはそのままgccを呼び出しているだけということです。 ここまで長々と書いてきてなんともあっけない結論ですがまあラッパーとはこんなものです。

しかしこのことにより、musl-gcc の仕様に記載のないことをしたい場合にはどうすればよいかがただちにわかります。 「(さほど余計なことをせず)そのままgccを呼び出している」以上、gccのオプションを調べればよいわけです。

前項では静的ライブラリlibc.aと静的リンクするには「gcc-musl」に対して「-static」オプションを指定しましょうと説明しました。 今回の項で説明した gcc-musl の全貌により、 その指定は確かに正しい(「-static」オプションについてはgccにそのまま渡されている)ことが裏づけられたことになります。

gccで静的リンクするためのオプションが「-static」であることはGoogle検索すればすぐ出てくるでしょう。 GNUのサイトhttps://gcc.gnu.org/onlinedocs/gcc/Link-Options.htmlにも (大してわかりやすくない説明ですが)書かれてあります。

しかしここで重要なことは、別に「-static」オプションに限らず、 その他のオプションに関しても同様にgccにそのまま渡すことができ、 しかもそのようにしてもまず変な副作用もなかろうということです。 もっと言えば、gcc-muslで追加されているオプションさえ適切に渡すならば、 gcc-muslを介さず gcc やそれと互換性のある llvm_clang などを直接呼び出して実行させることができるということです。

znk_projectのMakefile_linux.makではこの検証での結論に基づき、gccやllvm_clangを直接呼び出すことでmuslを使用しています。 ただし勿論、muslに備えた適切なオプションを指定する必要はあり、 そのようなオプションをMakefile_linux.mak内で適切に切り分けています。


目次に戻る

Alpine Linux版をコンパイルする

難易度:★★

はじめに


Alpine Linux はLinuxのディストリビューションの一つです。 Moaiのビルド方法についても基本的には他のLinuxと同じになります。

ただし他のLinuxでは基本的にC言語標準ライブラリとしてGNU系(glibc)が使われるのに対し、 Alpine Linuxではmuslが使われます。 そのため、他のLinuxでビルドした実行バイナリは Alpine Linux では(一部の例外を除き)基本的に起動できませんし、 その逆も然りです。 その辺りで多少注意が必要なため、この記事でもAlpine Linuxだけ個別の章を設けています。

必要なコマンド群


イントロダクションでは、C/C++での開発において最低限必要となる基本コマンドについて述べました。

ここで今一度、Alpine Linux環境におけるこれらのコマンドについて確認いたしましょう。

ツールの種類 対応コマンド 対象ファイル
Cコンパイラ gcc c から o へ
C++コンパイラ g++ cpp から o へ
リンカ ld o から 実行バイナリ/soへ
ライブラリアン ar および ranlib o から aへ
ビルドスクリプトmake ( GNU make )Makefile_linux.makを使用
標準ライブラリ musl ライブラリ システム(/usr配下など)


これらのコマンドが実行できるように必要に応じてインストールすればよいということになります。

Alpine Linux(この記事執筆時点での最新バージョンはVer3.24)を最小構成でインストールした場合、上記のコマンドはインストールされていません。 今回は Alpine Linux 限定の話ですので、これらのインストール方法についても詳しく説明しておきましょう。 Alpine Linux ではパッケージ管理として apk コマンドを使用します。

Alpine におけるコマンド apk は Alpine Package Keeper の略です。 一方、Android で apk ファイルというものもありますが、こちらは Android Package Kit の略です。 この記事では両方登場して紛らわしいですが、両者は全く関係ありません。

コンパイラなどプログラミング系のツールは apk コマンドにより build-base パッケージをインストールすれば一通り揃うようです。 su コマンドで root になり以下のように実行してください。

apk add build-base

尚、Alpine Linux(Ver3.24) ではデフォルトでは sudo コマンドは使えません。 またデフォルトのリポジトリ( main repository)からは sudo をインストールすることもできません。 community repository からならば sudo をインストールすることができるようですが、 今回は面倒ですので sudo コマンドなしで build-base をインストールするものとします。
base-build をインストールしましたら gcc や ranlib や make コマンドなどが使用可能なことを確認してください。 root が必要なのはここまでです。 ここからは exit で一般ユーザに戻ります。

コンパイルおよびインストール


上記「基本知識」で述べた必須コマンドはすべてシステムにインストールされているものとして話をすすめます。

  1. ターミナル(xterm, urxvtなど)を開き、インストール先のパスを環境変数 ZNK_INSTALL_DIR で指定します。

  2. export ZNK_INSTALL_DIR=$HOME/install_dir
    

    環境変数 ZNK_INSTALL_DIR で指定されたディレクトリが存在しない場合は自動的に生成されます。 Moaiの場合、$ZNK_INSTALL_DIR/moai-v$REL_VER($REL_VERはMoaiのバージョン番号で例えば2.3など)という名前のディレクトリが生成され、 必要なファイルがそのディレクトリ配下へとインストールされます。

    インストールされるファイルのうち、実行バイナリやライブラリなどプラットフォームに依存するファイル群は、 $ZNK_INSTALL_DIR/moai-v$REL_VER/bin/$PLATFORM という名前のディレクトリに配置されます。 ここで $PLATFORM はプラットフォームの種類を表します。 Linuxにおいては、環境変数ZNK_MUSL_DIRを指定した場合、$PLATFORM は linux_musl-* という文字列(*はCPUのタイプ)になり、 環境変数ZNK_MUSL_DIRを指定しない場合、$PLATFORM は linux-* という文字列になります。

    Alpine Linux では、環境変数ZNK_MUSL_DIRを指定せずとも デフォルトで musl ライブラリを使用したビルドを行います。 この場合、$PLATFORM の値は上述の通り linux_musl-* ではなく linux-* などとなります。

    Alpine Linux の場合、linux_musl-* と linux-* のどちらも musl を使ってのビルドとなります。 しかし linux-* の場合、通常は Alpine Linux 上でしか自立起動できないものとなります。

    前のセクションでも述べた通り、これは musl 本体の問題というよりも動的リンカの種類と位置の問題です。 linux-* での実行バイナリのINTERPセクションの値は「/lib/ld-musl-x86_64.so.1」というように動的リンカの絶対パスが直に指定されたものになります。 他のLinuxでは通常このような名前の動的リンカは存在しませんから、 (Alpine Linuxと同じ動的リンカをそちらのLinuxにもインストールしない限りは)自立起動もできないことになります。

    尚、自立起動に拘らないならば他のLinux上でも起動は可能です。

    すなわち musl の動的リンカをいっしょに付属させ (muslはMITライセンスですので glibc とは異なりそのようなことが可能です)、 その動的リンカをインタプリタ言語のようにシェルから起動し、その引数として 実行バイナリを明示的に指定した上で起動する方法を前章で述べましたが、 その方法ならば他のLinux上でも起動させることができます。
    Alpine Linux の場合、linux-* と linux_musl-* のどちらもmuslを使ってビルドされたバイナリであるため、 両者の違いは他のLinuxほどはありません。 以下に両者の違いを列挙します。

    • linux_musl-*の場合(すなわち環境変数ZNK_MUSL_DIRを指定した場合)、 znk_projectが内蔵するmusl(そのソースコードはznk_project/musl以下にあります)をビルドします。 そのmuslは環境変数ZNK_MUSL_DIRで指定された場所に一旦インストールされますが、 以降のビルドではそのmuslを使います。

      尚、この内蔵muslはmusl Ver1.2.6をベースとして、 オリジナルのmuslにあった問題を少しだけ手直ししたものです。 具体的には以下の点について修正しています。

      • オリジナルではいくつかのコンパイル時警告が発生する。 無視しても構わないタイプのものだが、 ヘッダ include/endian.h で発生するものについては、 これ以降のコンパイル時のメッセージの邪魔になるので修正。

      • オリジナルではコンパイルオプションとして-sonameが指定されていない。 今、他のライブラリAや実行バイナリBから動的ライブラリXを動的リンクする場合を考える。 Xにsonameを指定しなかった場合、そのXの場所を表すパスの情報(DT_NEEDEDと呼ばれ、readelf -d コマンドで確認が可能) の値がAやB内に絶対パスで埋め込まれてしまう (gccないしはldがそのような仕様になっている)。 一方、Xにsonameを指定した場合、AやB内に埋め込まれる DT_NEEDEDの値はXのsonameとなる(つまり相対パスのような形で埋め込むことができる)。 DT_NEEDEDの値が絶対パスでは他のPCに移した場合に問題が生じる。 そのため、-sonameの指定は必ず行わなければならない。

      • オリジナルでは(おそらく少しでもパフォーマンスを重視しているためか) 動的ライブラリと静的ライブラリで異なるオブジェクトファイルを生成している (動的ライブラリの方のオブジェクトファイルは区別のため拡張子をloとしている)。 この違いは、動的ライブラリの方では -fPIC オプションを付けてコンパイルし、 静的ライブラリの方ではそれを付けずにコンパイルしている点だけである。 しかしそれだと同じC言語のファイルを二回冗長にビルドすることになるわけで、 単純にビルド全体にかかる時間も倍となる。 znk_project ではどのみち静的ライブラリの方はそれほど使用しないため、 このわずかなパフォーマンスの差も特に問題とならない。 そのため、動的ライブラリと静的ライブラリのどちらも -fPIC オプションを付けたオブジェクトファイルを 使用する形に修正している。
      一方、linux-*の場合(すなわち環境変数ZNK_MUSL_DIRを指定しない場合)、 Alpine Linuxシステム標準のmuslをそのまま使います。

      よって、linux-*の場合、znk_project内蔵muslのビルドも行われず、 libZnkのビルドからのスタートとなり、ビルド時間がそれだけ短く済みます。

      ただし、linux_musl-*の場合でも、単に「sh make_world.sh clean」だけでは musl 配下のオブジェクトファイルまでは clean されないため、 通常、musl の再ビルドは行われないことになります (これはznk_project/muslだけではなくznk_project/libresslなどでも同様です)。 そのため、二回目以降の「sh make_world.sh install」の実行では、 libZnkのビルドからのスタートとみなせます。

      尚、musl 内を本当に clean したい場合は該当ディレクトリに直接移動し、 そこにあるMakefileを実行して直接cleanします。

    • linux_musl-* の場合、INTERPの値として「libc.so」がセットされます。 この場合、カレントディレクトリにlibc.soがある場合限定ですが、どんなLinuxでも自立起動ができます。 一方、linux-* の場合、INTERPの値として「/lib/ld-musl-x86_64.so.1」がセットされます。 この場合、Alpine Linux(あるいはこれに該当する動的リンカがインストールされているシステム)限定ですが、 どのディレクトリにいても自立起動ができます。

    • linux_musl-* の場合、rarakuコマンドなど一部のコマンドがシェルスクリプトでラップされ、 実体のネイティブコマンドが raraku.bin などといった名前にリネームされます。 これにより、少し凝った仕掛けにはなってしまう嫌いはありますが、見かけ上自立起動できるかのように振舞わせることが可能となります。 一方、linux-* の場合、そのような変なことはせず、その他のシステムと同様に ネイティブライブラリをありのままの名前で作ります。

    • linux_musl-* の場合、muslインストールディレクトリから libc.so が自動的にMoaiインストールディレクトリにコピーされます (よってインストールディレクトリをそのまま他のPCに持って行けば libc.so もいっしょに付属することになります)。 linux-* の場合はそのようなコピーは行われません。

    Rarakuの場合も同様で、$ZNK_INSTALL_DIR/raraku-v$RRK_VER($RRK_VERはRarakuのバージョン番号で例えば1.0など)という名前のディレクトリ配下にインストールされます。

    尚、環境変数 ZNK_INSTALL_DIR が指定されていない場合は、src ディレクトリの一つ上の階層に install_dir というディレクトリが生成され、 その配下に moai-v$REL_VER や raraku-v$RRK_VER などが配置されます。

    尚、環境変数INST_MACHINEに「x86」または「x64」を明示的に設定して上記を実行した場合は、それぞれ32bit版または64bit版バイナリが生成されます。 この環境変数を指定していない場合は、Makefile_linux.mak 内で uname -m コマンドが実行され、その結果により 32bit版か64bit版かが自動的に決定されます。

  3. srcディレクトリへ移動し、以下のシェルスクリプトを実行します。

  4. cd znk_project/src
    sh ./make_world.sh install
    

    これですべてのコンパイルおよびインストールが自動で行われます。

    このシェルスクリプトにより、Makefile_linux.mak の存在する各ディレクトリへ自動的に移動しつつ make -f Makefile_linux.mak install が実行されます。 コンパイル後の実行バイナリは各ディレクトリ内のout_dirに格納されます。
    インストール処理だけを省略したい、つまり純粋にコンパイルだけを行いたい場合は、 各々のディレクトリで直接「make -f Makefile_linux.mak」を実行した方がよいでしょう。 「sh ./make_world.sh」と実行することもできますが、 純粋にコンパイルだけでは必要なライブラリやツールがまだ所定の位置に配置されないため、 それらに依存するツールのコンパイルにおいて、リンクに失敗するかもしれません。

以上でMoaiのコンパイルとインストールはすべて完了です。

動作確認


念のため、コンパイルおよびインストールされたmoaiが起動するか確認しておきましょう。 インストールディレクトリ配下のmoai-v$REL_VER ディレクトリへと移動し、moaiを起動します。 例えばMoaiのバージョン番号が2.3の場合であれば以下のようになります。

cd $ZNK_INSTALL_DIR/moai-v2.3
sh moai.sh

以下のようなメッセージが表示されればmoaiは無事起動しています。

Moai : config load OK.
Moai : target load OK.
Moai : analysis load OK.
Moai : Filter Loading [filters/….myf]
Moai : Plugin Loading [plugins/….so]
…
Moai : acceptable_host=[LOOPBACK]
Moai : blocking_mode=[0]
Moai : AutoGet PrivateIP=[192.168.…].
Moai : Listen port 8124...

Moai : ObserveR : 1-th sock events by select.

最初からacceptable_hostをANYの状態(LAN内の他のPCやスマホからもmoaiにアクセスできる状態)にして起動したい場合は、 以下のように -acceptable_any オプションを指定して実行することもできます。

cd $ZNK_INSTALL_DIR/moai-v2.3
sh moai.sh -acceptable_any

これにより、上記の起動メッセージにおいて、 「acceptable_host=[LOOPBACK] 」の部分が 「acceptable_host=[ANY] 」となって起動されるはずです。


目次に戻る

BSD版をコンパイルする(FreeBSD/NetBSD/DragonFly/OpenBSD)

難易度:★★

はじめに


Ver2.3よりBSD系のOS(FreeBSD/NetBSD/DragonFly/OpenBSD)でのMoaiのビルドもサポートします。

ただし DragonFly や OpenBSD での使用はあまりお勧めしません。 既にこれらを使用し、注意点など十分把握されている方ならば何も問題ないですが、 これからBSD系のOSを入れて試してみようという方はこれらではなくFreeBSD や NetBSD の使用を強くお勧めします。 ただでさえ色々と注意点の多いBSD系のOSですが、DragonFly や OpenBSD の場合はさらに多くなります。

尚、Linux と BSD のどちらを使おうか迷っている方は Linux の使用をお勧めします。 FreeBSDやNetBSDは BSD系 OS の範疇に限ればかなり丁寧に出来ているとは思いますが、 それでも Linux の使いやすさには及んでいません (ただしその Linux 自体にも様々なディストリビューションがあり、ピンからキリなわけですが)。
参考: DragonFlyについて

違う!ソイツじゃない!

DragonFlyは比較的最近登場したBSD系のOSで、 他のBSD系との違いをざっくり言うなら「ディスクスペースを消耗しまくったり中で色々変なこと(例えば普通とは違う凝った並列処理など)をしてでも、 なるべく高速化をめざすOS」といった理解でとりあえずよいのではないでしょうか? しかし「ディスクスペースを消耗しまくったり中で色々変なことをする」ということは、 ユーザに課される注意点もそれだけ増えるということです。

ちなみにこの記事でのスタンスはほぼその逆で、 ディスクスペースをなるべく節約し、確実に動作させることをめざします。 とはいえ、我々は別にDragonFlyのとっているスタンスに反対しているわけではなく、 そのようなスタンスにも一定の賛同はします。

そもそも昨今のマシンやOSなら、そこまで変なことしなくてもほとんどの用途において十分高速なはずです。 そのため、通常はDragonFlyを使う必要性はほぼないとは思いますが、 その一方で、非常な高速性を要求される用途では必要なこともあるのだろうとは思います。

極端な話、ユーザは100MBのディスクスペースを使っているつもりが、 実際には DragonFly 内部でそれよりもかなり多くのスペースを消耗しているといったことが起きるかもしれません。 また rm コマンドを実行したのにディスクの空きが増えるどころか 減ってしまうという奇妙なことが起きます

これは DragonFlyで独自に使用されるファイルシステム Hammer2 に起因します。 Hammer2 を使用している場合、スナップショットと呼ばれるある種の履歴情報が 実際のファイルとは別に保持されます。 そのため余計なディスクスペースを消耗するのです。

また、このファイルシステムでは rm コマンドを実行しただけではファイルの実体は消去されません。 Windows などで言うところの「ゴミ箱」のような場所へ移動され、さらには「削除をした」という履歴情報が スナップショットに追加されますので、 結局、ディスクの空きが増えるどころかむしろ減ってしまうわけです。 これを完全に消去するには、「hammer2 bulkfree /」というかなり見慣れない(というよりほぼDragonFlyを使っているユーザにしかわからない) コマンドを実行する必要があります。 これがWindows などで言うところの「ゴミ箱を空にする」です。

通常、大きなファイルを rm コマンドで削除するのは、 ディスクの残りが切迫している場合が多いと思います。 そのような状況でユーザの意に反して逆にディスクの残りが減ってしまうのは、 状況を把握していないユーザからすればかなり混乱する挙動でしょう。

ちなみに Hammer2 以外のファイルシステムとして、Hammer や UFS を DragonFly のインストーラから選択できるようになっています。 ところがこれらを選択した場合、インストール先のハードディスク容量として、 Hammerの場合は 10GB、UFSの場合は 4GB以上のサイズが必須であるといった旨のメッセージが表示されます (もしもインストール先のディスクがそのサイズに満たない場合、これらのファイルシステムを選択することができません)。 ディスクスペースの消費を最小限としたい(目安としては一つのBSDにつきせいぜい3GB以下で済ませたい) 我々としてはこの要求水準は高すぎるため、この記事での選択肢としては Hammer2 一択となります。
あとこれは環境にもよるかもしれませんが、OSインストール時のファイルのコピーに失敗する場合があります。 例えばVirtualBox上でのインストールの場合、ファイル等コピー時にVirtualBoxごとクラッシュすることがあります (何度か試すと最後まで成功する場合もあり、現象にあまり再現性がありません)。

我々は同じVirtualBox上でその他のBSDもインストールして比較検証しておりますが、 このようなことは OpenBSD でも起こります (OpenBSDの場合、VirtualBoxごとクラッシュするようなことはありませんが、 ファイルのコピーに失敗後、インストーラが強制終了することがあります)。 一方、FreeBSD や NetBSD ではこのようなことは一切起こりません。

DragonFlyの場合、もしかすると hammer2 を 3GB のディスクで使用していることにに由来するクラッシュかもしれませんが、 その場合でも普通は「ディスクサイズが足りません」といったエラーが出て インストーラが中断する程度のことで済むでしょうから、VirtualBox との相性の問題かもしれません。
Close


参考: OpenBSDについて
今回、筆者はMoaiのビルドと動作確認のため、四つのBSD(FreeBSD/NetBSD/DragonFly/OpenBSD)の最新版を順番にインストールしましたが、 その中で OpenBSDの(OSの)インストーラは、他三つのBSDに比べて特に問題があります。 Moaiのインストールとは関係ない話になってしまいますが、 OpenBSD のインストーラの問題点についてここで参考までに説明しておきましょう。

一番の問題は途中で(というよりインストールのほぼ最後のフェーズで) fw_update というプログラムが実行されることです。 このプログラムは機器に備わっているファームウェアの最新版をOpenBSDのサイトからダウンロードし、 さらに更新までを自動的に行うというもののようですが、 このような処理は途中で中断などすると機器が動作しなくなるリスクを伴います。 ところがOpenBSDの(OSの)インストーラは、あろうことかこれをユーザに何の断りもなく問答無用で実行してしまいます。

さらに悪いことに、筆者の環境で実際にこのプログラムを実行すると、 10分程度以上メッセージに進展がなく止まってしまいます。 これは恐ろしい事態です。 何のメッセージも出ないため、ダウンロードに時間が掛かっているのか、それとも何らかの問題でフリーズしているのかさえ 見かけでは判断できません。さすがに10分以上進展がないのは何かがおかしいと判断し、 最終的には Ctrl+C で中断しましたが、上述した通り、このプログラムは機器に備わっているファームウェアを更新する処理を伴います。 そのため、一口に中断といってもそこらにあるプログラムとは違い、相当に覚悟のいる決断になるわけです。 幸い、筆者の場合VirtualBox上での実行でしたのでおそらく物理的な機器そのものには問題なかったと思われますが、 これが実機での実行であった場合、機器そのものに何らかの異常が発生した可能性を否定できません (そもそもフリーズ(のような状況)になった原因が、VirtualBoxとの相性の問題であった可能性もあります)。

他のOSのインストーラでもファームウェアを更新することはあるかもしれませんが、 筆者の経験上このような事態はOpenBSD以外で見たことはありません。 OpenBSDはセキュリティを重視するOSと聞きますが、これでは出だしからかなり危ないではないですか!

この fw_update の自動実行を回避する方法がインストーラ自体に用意されていないのがまた困りものです。 普通はユーザに「fw_updateを実行しますか?」などといった質問メッセージを出すものでしょう。 それすら出さないのです(その割にこのインストーラ、事前にかなり優先度に低い質問をダラダラと聞いてきます)。 そのためネットワークの設定を敢えて失敗させた状態でインストーラのウィザードを進め、 fw_update の通信自体をそもそも遮断してしまうという裏技のような方法を使って fw_update の実行を ある意味安全に阻止させるしかありません (この場合、OpenBSDのインストールが全て終わったら、改めて手動でネットワークの設定を行います)。

その他、このインストーラの挙動は細かい点で色々と微妙です。 例えば「X Windows Systemを使いますか?」といった質問をして来るのですが、 これに「no」と答えたにも関わらず、後のコンポーネントの選択画面でX関係のものに全部チェックが入るため、 そこでそれらのチェックをわざわざ手動で外さなければなりません (なんのために事前に「X Windows Systemを使いますか?」と質問したのでしょう?)。

また、ユーザのhomeディレクトリを含むパーティションがデフォルトではかなり少なめに配分されます。 例えばディスク全体のサイズが3GBの場合、homeディレクトリを含むパーティションのサイズは300MB、 実際に使える要領としては少し減って270MBほどとなります。 znk_project 全体をビルドする場合、途中で一時的なオブジェクトファイルなどがかなり多く生成されるため、 ディスクの空きとして300MB程度は必要となりますからこれでは多分足らないことになります。 勿論これは用途や用意されたディスクのサイズにもよるでしょうが、 手動でパーティションのサイズを調整する方法についても把握しておく方がよいでしょう。

またOSのインストーラから話は変わりますが、 OpenBSD では make コマンドにも注意点があります。 BSD なのだからいわゆるBSD系の bmake コマンドがデフォルトでインストールされているのだろうと 思われるかもしれませんが、確かにほぼその通りなのですが、 困ったことに OpenBSD のものだけは FreeBSD/NetBSD/DragonFly とは少し違う独自のものとなります。

例えば変数内で指定するモディファイアのうち、 シェルを実行する「:!cmd!」、隣り合う重複要素を削除する「:u」がOpenBSDのmakeでは(BSD系makeにも関わらず)サポートされません。 また、パターンマッチを行う「:M」モディファイアはサポートされるものの、 ここに指定するパターンに変数を含ませた場合、その展開が行われません (一方でFreeBSDやNetBSDの「:M」においてはこれが展開されます)。

mkfgen では BSD Makefile の生成を行います。 四つのBSDすべてがこのBSD Makefile 一つでコンパイルできれば綺麗にまとまるのですが、 OpenBSD のみ足並みを乱す形で(主に上記の機能のサポートの欠落が原因で)、この Makefile がうまく機能しません。 かといって mkfgen で(既にBSD対応のMakefileがあるにも関わらず)OpenBSD のためだけに Makefile をもう一つ増やすのは流石にかなり蛇足感がありますので、 現段階では OpenBSD系のmakeに限っては、gmake のインストールを必要要件としています。

今回、筆者がちょっと触っただけでもこれだけの問題があります。 これから初めて BSD を使う予定であり、尚且つどの BSD を使うか迷っている方には、 OpenBSDは個人的に全くお勧めできません。 これは基本的に「OpenBSDのプロ」ご用達のOSであり、 使いこなすにはOpenBSDに対するが必要です。

Close


必要なコマンド群


イントロダクションでは、C/C++での開発において最低限必要となる基本コマンドについて述べました。

ここで今一度、BSD環境におけるこれらのコマンドについて確認いたしましょう。

FreeBSD(Ver15.0)
ツールの種類 対応コマンド 対象ファイル
Cコンパイラ llvm_clang c から o へ
C++コンパイラ g++ cpp から o へ
リンカ ld o から 実行バイナリ/soへ
ライブラリアン ar および ranlib o から aへ
ビルドスクリプト1bmake ( BSD make )Makefile_bsd.makを使用
ビルドスクリプト2gmake ( GNU make )Makefile_linux.makを使用
標準ライブラリ BSD Cライブラリ システム(/usr配下など)


NetBSD(Ver10.1)
ツールの種類 対応コマンド 対象ファイル
Cコンパイラ gcc(v10.5) c から o へ
C++コンパイラ g++ cpp から o へ
リンカ ld o から 実行バイナリ/soへ
ライブラリアン ar および ranlib o から aへ
ビルドスクリプト1bmake ( BSD make )Makefile_bsd.makを使用
ビルドスクリプト2gmake ( GNU make )Makefile_linux.makを使用
標準ライブラリ BSD Cライブラリ システム(/usr配下など)


DragonFly(Ver6.4.2)
ツールの種類 対応コマンド 対象ファイル
Cコンパイラ gcc(v8.3) c から o へ
C++コンパイラ g++ cpp から o へ
リンカ ld o から 実行バイナリ/soへ
ライブラリアン ar および ranlib o から aへ
ビルドスクリプト1bmake ( BSD make )Makefile_bsd.makを使用
ビルドスクリプト2gmake ( GNU make )Makefile_linux.makを使用
標準ライブラリ BSD Cライブラリ システム(/usr配下など)


OpenBSD(Ver7.8)
ツールの種類 対応コマンド 対象ファイル
Cコンパイラ llvm_clang(v19.1)c から o へ
C++コンパイラ llvm_clangg++ cpp から o へ
リンカ lld o から 実行バイナリ/soへ
ライブラリアン llvmのar/ranlib o から aへ
ビルドスクリプトgmake ( GNU make )Makefile_linux.makを使用
標準ライブラリ BSD Cライブラリ システム(/usr配下など)


これらのコマンドが実行できるように必要に応じてインストールすればよいということになります。 尚、BSD においては Linux 等と同様にオブジェクトファイルの拡張子は o、静的ライブラリの拡張子は a、動的ライブラリの拡張子は so となります。 実行バイナリの拡張子はありません。

いわゆるVCにおけるインポートライブラリに相当する拡張子は存在しません。 so がその役割を兼ねるためです。

ビルドスクリプトとして二つありますが、どちらを使っても構いません(ただしOpenBSDについては gmake と Makefile_linux.mak を使ってください)。 BSD系での make コマンドは、その実体は gmake( GNU make ) ではなく bmake( BSD make ) となります。 そのため、OpenBSDを除くBSD上で単に make コマンドを使う場合は、Makefile_bsd.mak の方を指定してください。 gmake を使うなら Makefile_linux.mak を Makefile として指定します。

ちょっとどういうことかわかりにくいと思いますので、 念のためにLinuxと比較しながら補足しておきます。

Linux 上では gmake ( GNU make ) は単に make という名前のコマンドになっています。 そのため、コンソール上で単に make と打てば gmake ( GNU make ) が起動します。 通常、bmake ( BSD make ) はインストールされていません (ただし別途インストールすることで Linux 上でも bmake を使うことはできます。例えば Arch Linuxでは「pacman -S bmake」などとします)。

BSD 上では bmake ( BSD make ) は単に make という名前のコマンドになっています。 そのため、コンソール上で単に make と打てば bmake ( BSD make ) が起動します。 通常、gmake ( GNU make ) はインストールされていません (ただし別途インストールすることで BSD 上でも gmake を使うことはできます。例えば NetBSDでは「pkg_add -v gmake」などとします)。

ただしOpenBSDだけはbmakeとも少し異なる仕様のmakeとなります。

尚、gmakeを使う場合はgmake用のMakefile、bmakeを使う場合にはbmake用のMakefileを書かなくてはなりません。 さらにそれらのMakefileの文法は不幸なことにやや異なります。 そのため、これら二つのコマンドのどちらもサポートするには二つのMakefileが必要になるわけです (znk_projectの場合、Makefile_linux.mak と Makefile_bsd.mak がそれに対応します)。


コンパイルおよびインストール


上記「基本知識」で述べた必須コマンドはすべてシステムにインストールされているものとして話をすすめます。

  1. ターミナル(xterm, urxvtなど)を開き、インストール先のパスを環境変数 ZNK_INSTALL_DIR で指定します。

  2. export ZNK_INSTALL_DIR=$HOME/install_dir
    

    環境変数 ZNK_INSTALL_DIR で指定されたディレクトリが存在しない場合は自動的に生成されます。 Moaiの場合、$ZNK_INSTALL_DIR/moai-v$REL_VER($REL_VERはMoaiのバージョン番号で例えば2.3など)という名前のディレクトリが生成され、 必要なファイルがそのディレクトリ配下へとインストールされます。

    特にコンパイルされた実行バイナリやライブラリなどは、$ZNK_INSTALL_DIR/moai-v$REL_VER/bin/$PLATFORM という名前のディレクトリに配置されます。 ここで $PLATFORM は実行バイナリの種類を表し、例えばFreeBSDにおいては、freebsd-x86、freebsd-x64 といった文字列になります。

    FreeBSD/NetBSD/DragonFly/OpenBSDの実行バイナリに互換性はありません。 そのため、これらに対応する$PLATFORMの値はそれぞれ freebsd-x64、netbsd-x64、dragonfly-x64、openbsd-x64 のようになり、 全て異なります。

    尚、x86系CPU64bit版の場合、Windows界隈ではx64、オープンソース界隈ではx86_64という表記が主に使われます。 しかし znk_project では x64 という表記の方が短く、しかも x86 という表記と文字数が同じになるという理由でWindows流の表記で統一しています (ただしDarwinの場合はクロスコンパイルとの相性がよいi386とx86_64という表記を使っています)。 その他、target-tripletと呼ばれる表記方法もよく使われますが、 どのみち統一性が取れているとは現状では言いがたく、 ベンダー名など無駄に長い情報まで付加される表記であるため、そちらも採用していません。

    Rarakuの場合も同様で、$ZNK_INSTALL_DIR/raraku-v$RRK_VER($RRK_VERはRarakuのバージョン番号で例えば1.0など)という名前のディレクトリ配下にインストールされます。

    尚、環境変数 ZNK_INSTALL_DIR が指定されていない場合は、src ディレクトリの一つ上の階層に install_dir というディレクトリが生成され、 その配下に moai-v$REL_VER や raraku-v$RRK_VER などが配置されます。

    尚、環境変数INST_MACHINEに「x86」または「x64」を明示的に設定して上記を実行した場合は、それぞれ32bit版または64bit版バイナリが生成されます。 この環境変数を指定していない場合は、Makefile_bsd.mak(またはMakefile_linux.mak)内で uname -m コマンドが実行され、 その結果により 32bit版か64bit版かが自動的に決定されます。

  3. システム標準にgcc が入っているのか llvm_clang が入っているのかを確認します。

  4. 単に「gcc」と実行し、「gcc: error: not input file」などといったエラーが出れば gcc が入っています。 あるいは単に「clang」と実行し、「clang: error: not input file」などといったエラーが出れば llvm_clang が入っています。 どちらも入っているケースはレアでしょうが、その場合はどちらでも構いません。 どちらも入っていない場合はどちらかをインストールしておく必要があります。

    例えば、FreeBSD(この記事執筆時点での最新版はVer15.0)やOpenBSD(この記事執筆時点での最新版はVer7.8)をお使いの場合は、 llvm_clang が標準でインストールされており、 gcc は標準ではインストールされていないと思います。

    古いバージョンのFreeBSDやOpenBSDでは gcc が標準でインストールされているかもしれません。

    逆に、例えばNetBSD(この記事執筆時点での最新版はVer10.1)やDragonFly(この記事執筆時点での最新版はVer6.4.2)をお使いの場合は、 gcc が標準でインストールされており、 llvm_clang は標準ではインストールされていないと思います。

    NetBSDの場合、"Minimal instration" でOSをインストールした場合は どのコンパイラも入っていない状態だと思います。 OSをインストール時に素直に "Compiler Tools" も追加しておいた方がよいでしょう。 それですべてが揃います。

    "Minimal instration" の場合は、gcc や 付属する開発ツール、 C言語の標準ヘッダを別途 pkg_add コマンドなどでインストールする必要がありますが、 これはやや難しいかもしれません。 特にC言語の標準ヘッダが厄介で、該当するものがNetBSDのパッケージ配布サイトに存在しないように思えます。 これについては結局isoファイルに含まれる comp.txz から抽出するか、 pkgin コマンド(pkg_addの使いにくさを改良したもののようですが)を利用するしかないのかもしれません。

    llvm_clang を使用する場合、環境変数 ZNK_LLVM_CLANG_DIR の設定が必須となります。

    llvm_clang が標準でインストールされている場合、 /usr/bin/clang などが存在する状態のはずですので、以下のように設定することになります。

    export ZNK_LLVM_CLANG_DIR=/usr
    

    Makefile_bsd.mak(またはMakefile_linux.mak)は環境変数 ZNK_LLVM_CLANG_DIR が定義されている場合、 llvm_clang を使用したビルドに自動的に切り替えるようになっています。 そのため、llvm_clang を使用する場合、この環境変数 ZNK_LLVM_CLANG_DIR の定義は必須です。 たとえ単に「clang」と打ってllvm_clangが実行できるようになっていたとしてもです。

    あるいは llvm_clang をパッケージでインストールした場合、 /usr/local/bin/clang などが存在する状態のはずですので、以下のように設定することになります。

    export ZNK_LLVM_CLANG_DIR=/usr/local
    

    あるいは /usr/pkg/bin/clang などが存在する状態であるかもしれません。 その場合は、以下のように設定することになります。

    export ZNK_LLVM_CLANG_DIR=/usr/pkg
    



    逆に gcc を使用する場合、環境変数 ZNK_LLVM_CLANG_DIR を設定してはいけません。 ZNK_LLVM_CLANG_DIR を設定してしまった場合は、一度ターミナルを閉じるか以下のようにして設定を解除しましょう。

    unset ZNK_LLVM_CLANG_DIR
    

    gcc が標準でインストールされている場合、 あるいはパッケージでインストールした場合、 いずれの場合も ZNK_LLVM_CLANG_DIR を設定せず、 つまり何もしない状態にしてください。 それによりデフォルトで gcc を使ったビルドが行われます。

  5. srcディレクトリへ移動し、以下のシェルスクリプトを実行します。

  6. cd znk_project/src
    sh ./make_world.sh install
    

    これですべてのコンパイルおよびインストールが自動で行われます。

    このシェルスクリプトにより、Makefile_bsd.mak(または Makefile_linux.mak)の存在する各ディレクトリへ自動的に移動しつつ make -f Makefile_bsd.mak install または (gmake -f Makefile_linux.mak install) が実行されます。 コンパイル後の実行バイナリは各ディレクトリ内のout_dirに格納されます。
    インストール処理だけを省略したい、つまり純粋にコンパイルだけを行いたい場合は、 各々のディレクトリで直接「make -f Makefile_bsd.mak」(または「gmake -f Makefile_linux.mak」)を実行した方がよいでしょう。 「sh ./make_world.sh」と実行することもできますが、 純粋にコンパイルだけでは必要なライブラリやツールがまだ所定の位置に配置されないため、 それらに依存するツールのコンパイルにおいて、リンクに失敗するかもしれません。

以上でMoaiのコンパイルとインストールはすべて完了です。

動作確認


念のため、コンパイルおよびインストールされたmoaiが起動するか確認しておきましょう。 インストールディレクトリ配下のmoai-v$REL_VER ディレクトリへと移動し、moaiを起動します。 例えばMoaiのバージョン番号が2.3の場合であれば以下のようになります。

cd $ZNK_INSTALL_DIR/moai-v2.3
sh moai.sh

以下のようなメッセージが表示されればmoaiは無事起動しています。

Moai : config load OK.
Moai : target load OK.
Moai : analysis load OK.
Moai : Filter Loading [filters/….myf]
Moai : Plugin Loading [plugins/….so]
…
Moai : acceptable_host=[LOOPBACK]
Moai : blocking_mode=[0]
Moai : AutoGet PrivateIP=[192.168.…].
Moai : Listen port 8124...

Moai : ObserveR : 1-th sock events by select.

最初からacceptable_hostをANYの状態(LAN内の他のPCやスマホからもmoaiにアクセスできる状態)にして起動したい場合は、 以下のように -acceptable_any オプションを指定して実行することもできます。

cd $ZNK_INSTALL_DIR/moai-v2.3
sh moai.sh -acceptable_any

これにより、上記の起動メッセージにおいて、 「acceptable_host=[LOOPBACK] 」の部分が 「acceptable_host=[ANY] 」となって起動されるはずです。


目次に戻る

Android版をコンパイルする

難易度:★★★

はじめに


Androidは基本的にLinuxですが、PC Linuxとは大きく異なる部分もあります。 そして通常、AndroidにおけるアプリはJavaをベースとして(エントリポイントとして)提供されます。 Javaベースなアプリでは、グラフィカルなアイコンがホーム画面に表示され、それをタップすることで起動します。 このような一般的なAndroidアプリのような挙動を可能とするには、最終的に apk ファイルと呼ばれるものを作成する必要があり、 そしてその作成までの道は残念ながらそれほど簡単ではなく、いくつかの段階を踏む必要があります。

Moaiは基本的にはC言語で書かれたネイティブアプリです。 このセクションではまず第1段階として、Androidにおけるネイティブバイナリのコンパイルを試みます。

この記事において単に「src\make_apk\moai\setenv.bat」などと記述している場合があります。 これはダウンロードしたMoaiソース(znk_project)配下における「src\make_apk\moai\setenv.bat」という意味になります。

必要なコマンド群


イントロダクションでは、C/C++での開発において最低限必要となる基本コマンドについて述べました。

ここで今一度、Android(正確にはAndroid NDK)環境におけるこれらのコマンドについて確認いたしましょう。

ツールの種類 対応コマンド 対象ファイル
Cコンパイラ gcc c から o へ
C++コンパイラ g++ cpp から o へ
リンカ ld o から 実行バイナリ/soへ
ライブラリアン ar および ranlib o から aへ
ビルドスクリプトmake ( GNU make )Makefile_android.mak(wi または Makefile_android_linux.makを使用
標準ライブラリ Bionic Android NDKインストールディレクトリ


即ち、これらのコマンドが実行できるように必要に応じてインストールすればよいということになります。 ただし実際にビルドを行う環境は、Android端末ではなくPC上です。

最近Chromebook上でもビルドが行えるようになったそうですが、この記事ではそこまでは解説しません。

これらのコマンド一式はAndroid NDKと呼ばれるものに含まれており、それを入手して使います。

Android NDKの入手


これはGoogleのサイトが公式になり、ダウンロードも同サイトから行えます。 これを手動でダウンロードしてもよいですが、ここではMoaiのソースコードに付属するinstall_ndkスクリプトを使い、 Android NDKを自動的にダウンロードおよびインストールする方法について説明します。

  • Windowsの場合
    1. Android NDKをダウンロードおよびインストールしたいディレクトリの親となるディレクトリを(手動で)予め作って用意しておきます。
    2. どこでも構いませんが、ここでは例えば E:\Android というディレクトリとしましょう。

      実際には、さらにこの直下に android-ndk-r14b というディレクトリが自動的に作られます。 今回の例ですと、E:\Android\android-ndk-r14b が最終的なAndroid NDKのインストールディレクトリとなります。 ただしこの android-ndk-r14b ディレクトリはまだ作らないでください。 E:\Android ディレクトリだけを作っておきます。

      このandroid-ndk-r14b内は最終的にはおよそ1GB程度になります (install_ndkスクリプトはandroid-ndk-r14b内にある不要なディレクトリを自動的に圧縮し、なるべくディスクサイズを抑えるようにしています。この圧縮を行わない場合2GB以上にもなってしまいます)。

      ただし途中の作業において、zipファイルをダウンロードおよび解凍するなどで一時的な作業スペースが必要となります。 そのため、実際には3GB程度以上のディスクの空きは必要です。

    3. 環境変数ZNK_ANDROID_NDK_DIRにAndroid NDKをダウンロードおよびインストールしたいディレクトリのフルパスを指定します。

    4. このパスは上記で作成したディレクトリの後ろにさらに「\android-ndk-r14b」を連結したものになります。 今回の例ですと、E:\Android\android-ndk-r14b を値として指定します。

      環境変数ZNK_ANDROID_NDK_DIRはWindowsの環境変数設定ダイアログから設定することもできますが、 OS全体にその効果を波及させたくない場合は src\make_apk\moai\setenv.bat ファイル内に記述しておきましょう。 これにより、MoaiのビルドでAndroid NDKを使う場合のみ一時的にこの環境変数を有効にさせることができます。 テキストエディタでこのsetenv.batファイルを開き、「set ZNK_ANDROID_NDK_DIR=」とある文を探します。 「=」の右側の部分が指定すべきフルパスとなるように修正するとよいでしょう。 今回の例では、以下のようになります。

      set ZNK_ANDROID_NDK_DIR=E:\Android\android-ndk-r14b
      

      batファイルにおける set コマンドでは「=」の右側に指定すべき値を記述します。 ただしこのとき「=」の左右に余分な半角スペースなどを挿入してはいけません。 半角スペースそのものが値の一部として認識されてしまうからです。 また右側に指定する値はダブルクォートで囲ってもいけません。 ダブルクォート文字そのものが値の一部として認識されてしまうからです。

      ちなみにbatファイルにおいて REM で始まる行はコメントアウトと呼ばれ、その行がなかったものとみなされます。 これは通常はコード内で説明を記述する用途で使われます。 その他、設定の候補を一時的に有効/無効にしたりするのにも便利です。

    5. src\make_apk\moai\install_ndk.batをダブルクリックして実行します。

      install_sdk.bat というファイルもありますが、そちらではなくinstall_ndk.batの方です。

      これによりAndroid NDKのダウンロードとインストールが自動的に行われます。


  • Linuxの場合
    1. Android NDKをダウンロードおよびインストールしたいディレクトリの親となるディレクトリを(手動で)予め作って用意しておきます。
    2. どこでも構いませんが、ここでは例えばホームディレクトリ直下のDownloads/Android というディレクトリとしましょう。

      実際には、さらにこの直下に android-ndk-r14b というディレクトリが自動的に作られます。 今回の例ですと、$HOME/Downloads/Android/android-ndk-r14b が最終的なAndroid NDKのインストールディレクトリとなります。 ただしこの android-ndk-r14b ディレクトリはまだ作らないでください。 ホームディレクトリ直下にDownloads/Android ディレクトリだけを作っておきます。

      このandroid-ndk-r14b内は最終的にはおよそ1GB程度になります (install_ndkスクリプトはandroid-ndk-r14b内にある不要なディレクトリを自動的に圧縮し、なるべくディスクサイズを抑えるようにしています。この圧縮を行わない場合2GB以上にもなってしまいます)。

      ただし途中の作業において、zipファイルをダウンロードおよび解凍するなどで一時的な作業スペースが必要となります。 そのため、実際には3GB程度以上のディスクの空きは必要です。

    3. 環境変数ZNK_ANDROID_NDK_DIRにAndroid NDKをダウンロードおよびインストールしたいディレクトリのフルパスを指定します。

    4. このパスは上記で作成したディレクトリの後ろにさらに「/android-ndk-r14b」を連結したものになります。 今回の例ですと、$HOME/Downloads/Android/android-ndk-r14b を値として指定します。

      以下のようなコマンドを実行する形になります。

      export ZNK_ANDROID_NDK_DIR="$HOME/Downloads/Android/android-ndk-r14b"
      

      Linux等のシェルスクリプトにおいては、「=」の右側に指定すべき値を記述します。 ただしこのとき「=」の左右に余分な半角スペースなどを挿入してはいけません。「=」の左側が独立したコマンドとみなされてしまうためです。 右側に指定する値はダブルクォートで囲って指定しておいた方が賢明でしょう。 その方が値に半角スペースなどを含む場合にも対応できるからです(半角スペースが存在しないことが明らかな場合はダブルクォートを省略することもできます)。

      ただし単に上記のコマンドを実行しただけですと、一度ターミナルを閉じたら次回もまたこれを打ち込む必要があります。

      この環境変数ZNK_ANDROID_NDK_DIRの指定は ~/.bashrc 内に記述しておくことで永続的な設定とすることができますが、 OS全体にその効果を波及させたくない場合は src/make_apk/moai/setenv.sh ファイル内に記述しておきましょう。 これにより、MoaiのビルドでAndroid NDKを使う場合のみ一時的にこの環境変数を有効にさせることができます。 テキストエディタでこのsetenv.shファイルを開き、「export ZNK_ANDROID_NDK_DIR=」とある文を探します。 「=」の右側の部分が「"$HOME/Downloads/Android/android-ndk-r14b"」となるように修正するとよいでしょう。 今回の例では、以下のようになります(上記のコマンドとまったくいっしょです)。

      export ZNK_ANDROID_NDK_DIR="$HOME/Downloads/Android/android-ndk-r14b"
      



    5. src/make_apk/moai ディレクトリへ移動し、install_ndk.sh を実行してください。

    6. install_sdk.sh というファイルもありますが、そちらではなくinstall_ndk.shの方です。

      以下のような流れになります。

      cd src/make_apk/moai
      sh install_ndk.sh
      

      これによりAndroid NDKのダウンロードとインストールが自動的に行われます。

ダウンロードすべきAndroid NDKのzipファイルは650MB程度あるため、回線の状況によってはかなり時間が掛かるかもしれません。 またダウンロードが終わった後、install_ndkスクリプトは不要なディレクトリの圧縮も行いますが、こちらもサイズが大きいため、 多少の時間が掛かります。

install_ndkスクリプトに頼りたくない場合や、万一これが正常に動作しない場合は、以下の「参考: Android NDKを手動でインストールする方法」を参照してください。

参考: Android NDKを手動でインストールする方法
まずinstall_ndkスクリプトを使ったインストールと同様に Android NDK をダウンロードおよびインストールしたいディレクトリの親となるディレクトリを予め作って用意しておきます。 どこでも構いませんが、ここでは例えばWindowsならば E:\Android、Linuxならば $HOME/Downloads/Android というディレクトリとしましょう。

次にお使いのPCに応じた Android NDK をダウンロードしてください(今回は上記で作っておいたディレクトリへダウンロードするものとします)。 Android NDKの入手先は以下になります。

Android NDK Windows版(32bit/64bitいずれも可)

https://dl.google.com/android/repository/android-ndk-r14b-windows-x86.zip

Android NDK Linux版(64bit版のみ)

https://dl.google.com/android/repository/android-ndk-r14b-linux-x86_64.zip

Android NDKにはいくつかのバージョンがありますが、 Moaiのソースコードでは、Android NDK r14b を使ってください。

他のバージョンのAndroid NDKでもMoaiをビルドできる可能性はありますが、少なくとも Android NDK r18b 以降はclangを使用している関係で、 現状のMoaiに付属するMakefileでは対応できません。

clang は Windows XP以前では使用できないため、Moaiでは現状ではclangの利用を避けています。 (これはclang が起動時、Windows XPに存在しないレジストリ操作に関する関数を動的リンクしようとするためです)。

上記のURLは、Android Developers の NDK Download のトップページhttps://developer.android.com/ndk/downloads/index.htmlから辿ることのできるリンクを抜粋したものであり、Googleが公式に提供しているものとなります。

この NDK Download のトップページにおいて「Old Unsupported Versions」とある項目にUnsupported NDK Downloads と書かれたリンクがあります。 やや古いバージョンであるため「Unsupported」となっていますが、コンパイルに支障は全くありません。 この「Unsupported NDK Downloads」のページの「r14b」の項目に上記 Android NDK r14b をダウンロードするためのリンクがあり、 その順番で辿っても同じzipをダウンロードすることができます。

Android NDKにはいくつかのAPI Levelというものが存在しています。 ファイルサイズがこれほど大きい理由は、それらのAPI Level毎に必要なライブラリを提供しているためです。 API Level が小さいものほど古いものであり、古いAndroid OSでも動作するバイナリの生成をサポートしたものとなります。 逆に API Level が大きいものほど新しいAndroid OSをサポートしたものになりますが、 API Level を大きくしすぎると 古いAndroid OSで動作しないバイナリが生成されてしまう可能性があります。

このあたりのバランス取りは難しいところですが、Moaiのソースコードにおいて Makefile_android.mak が現在使用しているのは API Level 9 と API Level 21 です。これらのLevelのライブラリは Android NDK (r14b) のトップディレクトリから辿って、 platforms/android-9とplatforms/android-21に存在します。

どのバージョンのAndroid NDKがどの範囲のAPI Levelをサポートするかについては、 それぞれのバージョンのNDKの Changeslog をよく確認する必要があります。 Changeslog は上記のダウンロードコーナーから(英語ですが)読むことができます。

Android NDK のファイルサイズはかなり大きいので注意して下さい。 今回ダウンロードするzipファイルは約675MB程度あり、さらにこれを解凍展開すると2GB弱にもなります。 つまりこれらを合わせると(特に対策しなければ)最終的に占めるサイズが約 3GB 程度にもなります。


Android NDKのzipをダウンロードしましたら、これを解凍します。 解凍後はandroid-ndk-r14bというディレクトリが一つ生成されます。 今回の例ですと、Windowsならば E:\Android\android-ndk-r14b、Linuxならば $HOME/Downloads/Android/android-ndk-r14b という構成になるはずです。 念のため android-ndk-r14b の直下にさらにplatformsというディレクトリが存在することを確認してください。

解凍ツールによっては、android-ndk-r14b-windows-x86 といったようなディレクトリを android-ndk-r14b の一つ上に余分に作るかもしれません。 その場合は android-ndk-r14b ディレクトリを一つ上の階層へ移動するなりして調整してください。

最後に環境変数ZNK_ANDROID_NDK_DIRにAndroid NDKのトップディレクトリを指定します。 今回の例ですと、Windowsならば E:\Android\android-ndk-r14b、Linuxならば $HOME/Downloads/Android/android-ndk-r14b を値として指定する形になります。 環境変数の設定方法についての詳細はスクリプトを使った場合と全く同様ですので割愛します。

これでAndroid NDKの手動によるインストールは完了です。

Close


参考: Android NDKのディスク占有を少しでも抑える
Android NDK のディスク占有を少しでも抑えるため、install_ndkスクリプトでは Android NDK内の不要なディレクトリを圧縮しています。 これらのスクリプトの実体は src/make_apk/moai/compressive_config や src/make_apk/moai/rrk_script 内にあります。

ここで言う不要なディレクトリとは具体的に何でしょうか?

Android NDK には API Levelと呼ばれるものが存在し、そのAPI Level毎にライブラリが付属しています。 しかし、実際には使用する API Level はわずか(Moaiの場合せいぜい2つ)であり、その他のAPI Levelは使用していません。 この使用していないAPI Levelのライブラリが格納されたディレクトリが不要なディレクトリとなります。

これらが占有するファイルサイズは明らかに無駄であるため、削除しておけばサイズを節約できるわけですが、 しかし完全に削除してしまうと将来使用するライブラリを変更したくなるときに困るかもしれません。 そのときにあらためてzip全体をもう一度ダウンロードし直してもよいかもしれませんが、このzip全体は675MBと巨大であり、 (非常に速い回線をもっているのでもなければ)ダウンロードおよび解凍するだけでも割と時間がかかります。 たとえばなんらかのテストで頻繁にAPI Levelを変更する必要が生じた場合、これでは困ります。

install_ndkスクリプトでは、既にandroid-ndk-r14bディレクトリが存在する場合は、改めてzipファイルをダウンロードはしません。 また現在選択しているAPI Levelの変更を検出すると自動的に圧縮解凍を切り替えます。 つまり選択されたAPI Levelに該当するライブラリのみを解凍し、選択から外されたAPI Levelに該当するライブラリのみを圧縮するという処理を自動的に行います (これは zip ファイル全体を再ダウンロード、解凍するのと比べれば非常に少ない時間で済みます)。

API Levelの選択は setenv.bat(Windowsの場合)またはsetenv.sh(Linuxの場合)内の環境変数 ZNK_ANDROID_APP_PLATFORM_OS4、ZNK_ANDROID_APP_PLATFORM_OS5 の指定により行うことができます。

API Levelのライブラリ(これらの実体はZNK_ANDROID_NDK_DIR/platforms配下にありますが)以外にも install_ndk は 現在の設定では使用されないコンパイラやソースコードも自動的に圧縮します(コンパイラの実体はZNK_ANDROID_NDK_DIR/toolchains配下、 ソースコードの実体は ZNK_ANDROID_NDK_DIR/sources にあります)。

install_ndk スクリプトはraraku言語を使って書かれており、rarakuにデフォルトで備わっているzip圧縮用関数を使って圧縮を行いますが、 もしもお使いのOSに7-Zipコマンドがインストールされているならば、そちらを優先し7z形式で圧縮します (7zの方が圧縮効率がよい場合が多いからです)。

Windowsの場合、C:\Program Files\7-Zip\7z.exe の存在の有無を調べ、それが存在する場合はそれを使うようにしてあります。 Linuxの場合、which 7z コマンドが成功するならば 7z コマンドを使うようにしてあります。 これに関しては、src/make_apk/moai/rrk_script/util.rrks にその実装があります。

ちなみにダウンロードに関しては、デフォルトではMoaiに付属するbirdmanと呼ばれるツールで行いますが、 Linuxの場合、もしもwgetコマンドがインストールされているならば、そちらを優先しwgetコマンドでダウンロードを行います。 これに関しても、src/make_apk/moai/rrk_script/util.rrks にその実装があります。

圧縮によって、(これがない場合と比べ)およそ1GB程度のサイズ削減が可能です。 元の(ダウンロードした)zipファイルを削除して構わないのであれば、(Windows版かLinux版によって多少変わりますが)おおよそ700MB程度削減されますので、 最終的に占有するディスクサイズは 1GB程度にまで抑えることができます。

ただしこれはあくまでAndroid NDKだけが占有するサイズです。

Close


この章では、単にAndroid版ネイティブバイナリを生成することがゴールとなります。 そのため、Android SDK、JDK(Java Development Kit)やJRE、Gradleなどのツールやライブラリはこの段階ではまだ必要ありません。 後にapkを作成されたい場合はこれらが必要になりますが、その場合でもMoaiの場合、まずはネイティブバイナリの生成に成功しないことには先に進めません。 残りのツールやライブラリはこの生成が成功した後に入手を検討しても遅くはないでしょう。 その段階で改めて必要なものの導入手順を述べます。

ちなみに、Android Studio はこの記事では一切使用しないため最後まで入手する必要はありません

Android版Moaiのコンパイル(Windows PC上で行う場合)


ここではWindows上でコンパイルする場合について述べます。 Linux上でコンパイルしたい方はAndroid版Moaiのコンパイル(Linux PC上で行う場合)を参照してください。

まず環境変数ZNK_ANDROID_NDK_DIRにAndroid NDKのトップディレクトリが正しく設定されているかどうかを今一度確認しましょう。

例えば、Android NDK のトップディレクトリが E:\Android\android-ndk-r14b であるとします。 この場合、環境変数ZNK_ANDROID_NDK_DIR の値として「E:\Android\android-ndk-r14b」を指定します。 android-ndk-r14bディレクトリ直下にplatformsというディレクトリが存在することも合わせて確認しておきましょう。

次にsrcフォルダの直下にあるmake_world.batをダブルクリックして実行します。


コマンドラインベースでのメニュー選択UIが表示されますので次の手順を行ってください。
  1. 「3. switch debug-mode」を何回か選んでDEBUG_MODEを「optimize」にします。
  2. 「4. switch mkfid」を何回か選んでMKF_IDを「android」にします。
  3. 「5. switch machine」を何回か選んでMACHINEを「armeabi-v7a」等にします(ご利用のスマホのCPUに応じて選びます)。
  4. 「1. make install」を選びます。
ここで言う「選ぶ」とは、一番先頭にある数字のキーを押すことを意味します。 またはその項目までカーソルキーで移動してEnterキーを押してもよいです。 メニュー項目 3, 4, 5 については「選ぶ」毎にその値が循環的に変化していきます。

apkファイルは異なる種類の複数のCPUに対応させることができます。 例えば armeabi-v7a、arm64-v8a、x86、x86_64の4つのCPU(MACHINE値により指定)でコンパイルしたバイナリを 一つのapkファイル内に含めることができます。 これらのバイナリのうち、実際に使われるものは一つです。 apkがスマホ実機上でインストールされたとき、そのスマホのCPUの種類によって適切なバイナリが自動的に一つだけ選ばれ、 Android上にインストールされます。

このような「様々なCPUに対応したapk」を作りたい場合は、まずはそれぞれのMACHINE値毎に make install を行って、 それぞれのMACHINE毎のネイティブバイナリを作成しておく必要があります (ご利用のスマホのCPUがわからない場合でも、現在のAndroidスマホは大抵これら4つのうちのどれかには対応している確率が高いです。 よってその場合でもこのような総合的なapkを作っておけばどれかのバイナリには該当することでしょう)。

次のセクション「Android版APKを作成する」では、armeabi-v7a、arm64-v8a、x86、x86_64 の4つのMACHINE値でコンパイルしたものを あらかじめ作成しておくことを前提としてあります(ただしconfig_gen.rrksを修正することによってこの指定を変更することも可能です)。

作業の具体的な流れも以下に示しておきましょう。

  1. 「3. switch debug-mode」を何回か選んでDEBUG_MODEを「optimize」にします。
  2. 「4. switch mkfid」を何回か選んでMKF_IDを「android」にします。
  3. 「5. switch machine」を何回か選んでMACHINEを「armeabi-v7a」にします。
  4. 「1. make install」を選びます。
  5. 「5. switch machine」を何回か選んでMACHINEを「arm64-v8a」にします。
  6. 「1. make install」を選びます。
  7. 「5. switch machine」を何回か選んでMACHINEを「x86」にします。
  8. 「1. make install」を選びます。
  9. 「5. switch machine」を何回か選んでMACHINEを「x86_64」にします。
  10. 「1. make install」を選びます。


Android NDKが適切にインストールされているならば、これですべてのバイナリのコンパイルが完了するはずです。 コンパイルの次にすべきことへお進みください。

Android版Moaiのコンパイル(Linux PC上で行う場合)


ここではLinux上でコンパイルする場合について述べます。 Windows上でコンパイルしたい方はAndroid版Moaiのコンパイル(Windows PC上で行う場合)を参照してください。

まず環境変数ZNK_ANDROID_NDK_DIRにAndroid NDKのトップディレクトリが正しく設定されているかどうかを今一度確認しましょう。

例えば、Android NDK のトップディレクトリが ~/Downloads/Android/android-ndk-r14b であるとします。 この場合、環境変数ZNK_ANDROID_NDK_DIR の値として「$HOME/Downloads/Android/android-ndk-r14b」を指定します。 android-ndk-r14bディレクトリ直下にplatformsというディレクトリが存在することも合わせて確認しておきましょう。

次に znk_project/src へ cd コマンドで移動し、以下のように make_world_android.sh を実行します。

sh make_world_android.sh install

これにより armeabi-v7a、arm64-v8a、x86、x86_64の4つのCPU用バイナリを一括で生成することができます。

参考: 各CPU用のバイナリを個別に作成する方法
各CPU(INST_MACHINEの値により指定)のバイナリを個別に作成するには、make_world_android_one.sh を実行します。

make_world_android.sh では、この make_world_android_one.sh を単にINST_MACHINE値を変えつつ複数回呼び出しています。

環境変数INST_MACHINEを何も指定しないままmake_world_android_one.shを実行すると、デフォルトでは armeabi 版のネイティブバイナリが生成されます。 ただし armeabi版はかなり古いAndroid向けです。 通常は armeabi-v7a版、arm64-va8a版、x86版、x86_64版などのネイティブバイナリを生成する形になると思いますが、 そのためには以下のようにします。

armeabi-v7a 版のネイティブバイナリを生成したい場合は、以下のように実行します。

env INST_MACHINE=armeabi-v7a sh make_world_android_one.sh install

arm64-v8a 版のネイティブバイナリを生成したい場合は、以下のように実行します。

env INST_MACHINE=arm64-v8a sh make_world_android_one.sh install

x86 版のネイティブバイナリを生成したい場合は、以下のように実行します。

env INST_MACHINE=x86 sh make_world_android_one.sh install

x86_64 版のネイティブバイナリを生成したい場合は、以下のように実行します。

env INST_MACHINE=x86_64 sh make_world_android_one.sh install

make_world_android_one.sh は長いファイル名ですが、キーボードの「Tabキー」を駆使して補完しながら打ち込むと楽でしょう。

Close


apkファイルは異なる種類の複数のCPUに対応させることができます。 例えば armeabi-v7a、arm64-v8a、x86、x86_64の4つのCPU(INST_MACHINE値により指定)でコンパイルしたバイナリを 一つのapkファイル内に含めることができます。 これらのバイナリのうち、実際に使われるものは一つです。 apkがスマホ実機上でインストールされたとき、そのスマホのCPUの種類によって適切なバイナリが自動的に一つだけ選ばれ、 Android上にインストールされます。

このような「様々なCPUに対応したapk」を作りたい場合は、それぞれのINST_MACHINE毎のネイティブバイナリを作成しておく必要があります (ご利用のスマホのCPUがわからない場合でも、現在のAndroidスマホは大抵これら4つのうちのどれかには対応している確率が高いです。 よってその場合でもこのような総合的なapkを作っておけばどれかのバイナリには該当することでしょう)。

次のセクション「Android版APKを作成する」では、armeabi-v7a、arm64-v8a、x86、x86_64の4つのINST_MACHINE値でコンパイルしたものを あらかじめ作成しておくことを前提としてあります(ただしconfig_gen.rrksを修正することによってこの指定を変更することも可能です)。

Android NDKが適切にインストールされているならば、これですべてのバイナリのコンパイルが完了するはずです。 コンパイルの次にすべきことへお進みください。

コンパイルの次にすべきこと


以上でAndroid版のMoaiのネイティブバイナリのコンパイルは一応完了です。

この時点で、android-v2.*-armeabi-v7a、android-v2.*-arm64-v8a、android-v2.*-x86、android-v2.*-x86_64 (ここで*はバージョン番号)といった名前の ディレクトリが、デフォルトでは znk_project 直下に作成され、それぞれのCPUに対応する Moai が生成されています。 また、dlib ディレクトリも同様に作成され、それぞれのCPUに対応する soファイル(Androidにおけるネイティブライブラリの実体です)が生成されています。

ここからさらにapkファイルを作成されたい場合は、Android版APKを作成するへお進みください。

このネイティブバイナリ単独でも(つまりapkファイルなしでも)、Android Terminal Emulator と呼ばれるAndroid上のアプリでかなり強引ですがMoaiを動作させることは可能です。

これはLinux環境のターミナルなどでプログラムを起動するのと同じ要領で行います。 この場合ホーム画面にMoai用の特別なアイコンが表示されることはありませんし、 Moaiを起動するには、まずAndroid Terminal Emulatorを開き、cdコマンドによりmoai実行バイナリが存在するディレクトリへと移動してから moai実行バイナリを直接起動する形になります。

これらのネイティブバイナリは、現時点ではまだPC上にあって、Android実機にあるわけではありません。 Android Terminal Emulator上で動作させたい場合、最後に出来上がったディレクトリ(例えば moai-v2.3-android-arm64-v8aなど)をAndroid実機に転送しなければなりません。 そのためにまずは一旦このディレクトリをzip化しておきましょう。 このzipをAndroid実機に転送し、実機上で解凍して使うわけです。

あとはこのzipファイルをどうやってAndroid実機に転送するかですが、 これについては Android実機へ転送するで詳しく解説していますのでそちらを参照してください。

参考: Android Terminal Emulator上でMoaiを強引に起動する
とりあえずネイティブバイナリをzip化したものをAndroid実機に転送できたとします。 ここでは、そこからさらに実際にMoaiを起動させるまでの手順を説明します。

詳細についてはインストール方法(Android版)を参照して頂いた方がよいですが、ここでも一応簡単に説明しておきます。
  1. まずAndroid実機においてAndroid Terminal Emulatorアプリを起動させます(「端末エミュレータ」という名前のアイコンになっているかもしれません)。

  2. cd コマンドよりホームディレクトリへ移動します (ホームディレクトリ以外では書き込み権限や実行権限がない場合があるためです)。 ホームディレクトリへ移動するには、以下のように単にcdと入力してEnterを押します。

    cd
    

    今仮にAndroid実機に転送したzipファイルが moai-v2.3-android-arm64-v8a.zip という名前になっているとしましょう。 そしてそれが /sdcard/Download 配下に置かれているとします。 これを Android Terminal Emulator のホームディレクトリへコピーしなければなりません。 以下のように実行します。

    cp /sdcard/Download/moai-v2.3-android-arm64-v8a.zip ~/
    

  3. moai-v2.3-android-arm64-v8a.zip をホームディレクトリへコピーしたら、unzipコマンドや7zコマンドなどを実行し、 これを解凍します。

  4. 例えばunzipコマンドの場合、以下のように実行します。

    unzip moai-v2.3-android-arm64-v8a.zip
    

    7zコマンドの場合、以下のように実行します。

    7z x moai-v2.3-android-arm64-v8a.zip
    

    ホームディレクトリに moai-v2.3-android-arm64-v8a という名前のディレクトリが出来上がり、 その中に実行バイナリが展開されているはずです。 そのディレクトリ内へ移動し、moai_for_android.sh を実行します.

    cd moai-v2.3-android-arm64-v8a
    sh ./moai_for_android.sh
    

うまくいけばこれでMoaiが起動するはずです。 上記「Linuxの場合」で説明したのと同様のメッセージが表示されれば成功です。

Close


参考: ndk-buildコマンド
Moai で提供されている Makefile_android.mak はWindows上でのコンパイル、 Makefile_android_linux.mak はLinux上でのコンパイルを行うためのMakefileです。 Android版Moaiのビルドに関してはこの二つがあれば十分となります(Android NDK MacOS版はサポートいたしません)。

Android NDK では、ndk-build コマンドを提供しており、一般的にはこれを使ってAndroid向けネイティブバイナリを生成するものとされています。 しかしながら Moaiのソースコードでは ndk-build によるコンパイルをサポートしません。 ndk-buildに付属するMakefile(Application.mk、Android.mk)では、installターゲットなどを個別に記述することが難しいためです (おそらく別途Makefileを用意するなどしなければならないと思われます)。

さらには ndk-build では、Android NDKのバージョンによっては gcc ではなく clang を内部で呼び出すことがあります (少なくともr14bのndk-buildではデフォルトではclangが使われてしまいます)。 clang はWindows XPでは使用できません。 つまり Windows XP上では ndk-build によるビルドが(この場合、NDK内にあるMakefileを修正しない限り)不可能となってしまいます。

とはいえ、ndk-build は Android NDK で提供される一般的な方法であり、Android NDK の動作確認用のツールとしても使用できます。 そこで、ここではその使い方の概要だけ簡単に説明しておきます。

まずは環境変数 ZNK_ANDROID_NDK_DIR に Android NDK のトップディレクトリのパスをセットしましょう。 ndk-build コマンドの実体は ZNK_ANDROID_NDK_DIR 直下、あるいは ZNK_ANDROID_NDK_DIR/build ディレクトリの下にあります。 つまり以下のように環境変数PATHをセットすれば、ndk-buildが実行できるようになるはずです。

export PATH=$PATH:$ZNK_ANDROID_NDK_DIR:$ZNK_ANDROID_NDK_DIR/build

これでカレントディレクトリに(適切なMakefileを含んだ)jni ディレクトリが存在するならば ndk-build コマンドによりコンパイルを行うことができます。 現状、znk_project内のすべてのライブラリやツールに jni ディレクトリが付属しているわけではありませんが、 例えば libZnk の場合は mkf_android 内に jni ディレクトリがあり、次のように実行すると一応Androidのネイティブバイナリを生成することはできます。

cd libZnk/mkf_android
ndk-build

ndk-buildを実行すると、jniディレクトリと同列の位置に libs, obj ディレクトリが生成されますが、 その中にコンパイルしたバイナリやライブラリなどが出力されます。 ただし現在のlibZnkではそれ以上の機能をサポートしていません。 いわゆる make install(Moaiアプリとして適切なディレクトリ構成をするように必要なファイルなどを設置するなどといったこと)が ndk-buildコマンドを利用した方法ではできないということです。

現在のlibZnkにおけるjniディレクトリは、あくまでAndroid NDKの最低限の動作確認用のために置かれてあります。

Close


目次に戻る

Android APK版を作成する

難易度:★★★★

Introduction of Making APK


一つ前のセクションでは、android版ネイティブバイナリのコンパイルを行いました。 しかしこれだけですと、Android上でMoaiを起動するには複雑な手順が必要となります。

Androidのアプリというものは、普通は apk ファイルという形をとって配布されインストールされます。 apkファイルであれば、これをタップするだけでインストールが行えますし、インストールしたアプリはメニューやアイコンから簡単に起動することができます。 apkファイルは、多くの場合 Google Play と呼ばれるサイトから提供されるわけですが、別にこれは必須ではありません。 Google Play 以外のサイトからでも apk ファイルをダウンロードしてアプリをインストールすることができます(いわゆる野良アプリと呼ばれるものです)。

Google Play を経由して apk を配布したい場合、Googleが決めた特別な取り決めなどを色々と満たす必要があります。 Google Play には商業的な側面もあり、そのような目的のためには考慮しなければならないことも多いのでしょう。

ここでは apk ファイルの作成方法と、このファイルによるandroid実機へのインストール方法について技術的な解説をします。 また、armeabi-v7a、arm64-v8a、x86、x86_64 の4つのMACHINE値でコンパイルしたネイティブバイナリを既に作成済みであることを前提とします。 (ただし4つもいらないという方や、逆にarmeabi版も追加したいといった方などは、config_gen.rrks内のapp_abiリストを修正することによって、必要なネイティブバイナリの種類を変更することは可能です)。 ネイティブバイナリ生成がまだな方は一つ前のセクションAndroid版をコンパイルするを参照して下さい。

ここではAndroid Studioは使用せず、最小限の構成で開発を進めます。 必要なのはOpenJDK(java development kit)、Gradle、Android SDK(Command line tools only), Android NDKとなります

このセクションでも小さなC言語ファイルを一つだけネイティブバイナリにコンパイルせざるを得ないため、Android NDKも必要となります。 このC言語ファイルは、シンボリックリンクやパーミッションなどいくつか必要な調整をした後、 JAVAからlibMoai内のMoaiServer_main関数(これがMoaiの本体ですが)を呼び出す媒介として使います。

開発で使うOSの必要要件(バージョン等)を以下に述べておきます。

  • Windowsの場合

  • Moai本体の(ネイティブバイナリの)コンパイルはWindows2000でも可能ですが、このapk版の作成に関してはWindows XP以降が必要となります。 apk の作成においては OpenJDKが必須となりますが、 現在、ネット上で配布されているOpenJDKのうち、Windows2000で動作するものの入手は困難です。

  • Linuxの場合

  • 64bit版Linuxが必要となります。 apk の作成においては OpenJDKが必須となりますが、 現在、ネット上で配布されているOpenJDKのうち、Linux 32bit版で動作するものの入手は困難です。 またそもそもAndroid NDK自体が32bit版Linuxに対応しておりません。

APKを作成するために必要なツール/ライブラリ群のインストール


ネイティブバイナリを作るために必要なのはAndroid NDKだけでしたが、apkの作成となるとさらに多くのツール/ライブラリ群をインストールする必要があります。 ここではこれを自動的にダウンロードおよびインストールするためのinstall_sdkスクリプトを使う方法を解説します。
  • Windowsの場合
    1. 必要なツール/ライブラリ群をダウンロードおよびインストールしたいディレクトリを決め、その親となるディレクトリを(手動で)予め作って用意しておきます。
    2. どこでも構いませんが、この記事では例えば E:\Android というディレクトリとしましょう。

    3. 上記で作成したディレクトリの直下にさらに以下のディレクトリを(手動で)予め作って用意しておきます。 またそれぞれについて必要な環境変数を設定します。

      • OpenJDKディレクトリ

      • このディレクトリ配下にOpenJDKをインストールするものとします。 このディレクトリは空のままで構いません。 今回の例では、E:\Android\OpenJDK という構成になります。

        環境変数ZNK_OPENJDK_DIRにこのディレクトリのフルパス(E:\Android\OpenJDK)を指定してください。 src\make_apk\moai\setenv.bat 内に記述しておくとよいでしょう。 指定方法の詳細についてはAndroid NDKの設定の時と同様なので説明は割愛します。

        インストール後のサイズはおよそ200MB程度になります。

      • gradleディレクトリ

      • このディレクトリ配下にGradleをインストールするものとします。 このディレクトリは空のままで構いません。 今回の例では、E:\Android\gradle という構成になります。

        環境変数GRADLE_USER_HOMEにこのディレクトリのフルパス(E:\Android\gradle)を指定してください。 src\make_apk\moai\setenv.bat 内に記述しておくとよいでしょう。 指定方法の詳細についてはAndroid NDKの設定の時と同様なので説明は割愛します。

        インストール後のサイズはおよそ100MB程度になります。 ただしgradleを初回実行時、Android Gradle Pluginと呼ばれるものも自動的にダウンロードされますが、 それらの関連ファイルを含めると最終的に200MB以上に膨れ上がる場合もあります。

      • sdk_rootディレクトリ

      • このディレクトリ配下にAndroid SDKをインストールするものとします(Android NDKと名前が似てますが別ものです)。 このディレクトリは空のままで構いません。 今回の例では、E:\Android\sdk_root という構成になります。

        環境変数ANDROID_SDK_ROOTにこのディレクトリのフルパス(E:\Android\sdk_root)を指定してください。 src\make_apk\moai\setenv.bat 内に記述しておくとよいでしょう。 指定方法の詳細についてはAndroid NDKの設定の時と同様なので説明は割愛します。

        インストール後のサイズはおよそ400MB程度になります。

    4. src\make_apk\moai\install_sdk.batをダブルクリックして実行します。

      install_ndk.bat ではなくinstall_sdk.batの方です。

      これにより上記の各ディレクトリへ必要なツール/ライブラリ群のダウンロードとインストールが自動的に行われます。


  • Linuxの場合
    1. 必要なツール/ライブラリ群をダウンロードおよびインストールしたいディレクトリを決め、その親となるディレクトリを(手動で)予め作って用意しておきます。
    2. どこでも構いませんが、この記事では例えば ~/Downloads/Android というディレクトリとしましょう。

    3. 上記で作成したディレクトリの直下にさらに以下のディレクトリを(手動で)予め作って用意しておきます。 またそれぞれについて必要な環境変数を設定します。

    4. 環境変数の設定は src/make_apk/moai/setenv.sh 内に記述しておくとよいでしょう。 設定方法の詳細についてはAndroid NDKの時と同様なのでここでの説明は割愛します。

      • OpenJDKディレクトリ

      • このディレクトリ配下にOpenJDKをインストールするものとします。 このディレクトリは空のままで構いません。 今回の例では、$HOME/Downloads/Android/OpenJDK という構成になります。

        環境変数ZNK_OPENJDK_DIRにこのディレクトリのフルパス($HOME/Downloads/Android/OpenJDK)を指定してください。

        インストール後のサイズはおよそ200MB程度になります。

      • gradleディレクトリ

      • このディレクトリ配下にGradleをインストールするものとします。 このディレクトリは空のままで構いません。 今回の例では、$HOME/Downloads/Android/gradle という構成になります。

        環境変数GRADLE_USER_HOMEにこのディレクトリのフルパス($HOME/Downloads/Android/gradle)を指定してください。

        インストール後のサイズはおよそ100MB程度になります。 ただしgradleを初回実行時、Android Gradle Pluginと呼ばれるものもダウンロードされますが、 それらの関連ファイルを含めると最終的に200MB以上に膨れ上がる場合もあります。

      • sdk_rootディレクトリ

      • このディレクトリ配下にAndroid SDKをインストールするものとします(Android NDKと名前が似てますが別ものです)。 このディレクトリは空のままで構いません。 今回の例では、$HOME/Downloads/Android/sdk_root という構成になります。

        環境変数ANDROID_SDK_ROOTにこのディレクトリのフルパス($HOME/Downloads/Android/sdk_root)を指定してください。

        インストール後のサイズはおよそ400MB程度になります。

    5. src/make_apk/moai ディレクトリへ移動し、install_sdk.sh を実行してください。

    6. install_ndk.sh ではなくinstall_sdk.shの方です。

      以下のような流れになります。

      cd src/make_apk/moai
      sh install_sdk.sh
      

      これにより上記の各ディレクトリへ必要なツール/ライブラリ群のダウンロードとインストールが自動的に行われます。


ダウンロードすべきツール/ライブラリ群は多いため、回線の状況によってはかなり時間が掛かるかもしれません。

ダウンロードが完了したらインストールも自動的に行われますが、もしもこのインストール中にエラーが発生する場合、 他のバージョンのOpenJDK/Gradle/Android SDKが必要であるのかもしれません。 その場合、設定を変えて再度install_sdkを実行し直せばうまく行く可能性はあります。 その詳細な手順については、後の「トラブルシューティング」の項において説明します。

通常はこのinstall_sdkスクリプトを使うことを強くお勧めしますが、万一このスクリプトが正常に動作しない場合は、以下の手動による方法を参照してください。

参考: OpenJDKを手動でインストールする方法
OpenJDK(Java Development Kit)とは、いわゆるJavaコンパイラとランタイムです。 Gradleを動作させたり、javaのソースコードをコンパイルするのに必要となります。

Javaは現在Oracleが所有していますが、昔と比べ状況は混乱しています。 現在入手できるJavaバイナリは大きく分けて2種類あり、Oracleバイナリライセンスで配布されるものとOpenJDKと呼ばれるGNUライセンスで配布されるものです。 前者は商用ではフリーで利用できない制限があるためこの記事では選択肢から除外し、OpenJDK(GNU License下で配布されるもの) を使います。

OpenJDKがGNU Licenseであることを気にする必要はありません。 javaコンパイラでjavaソースをコンパイルした結果生成されるclassファイルやjarファイルに関して、GPL汚染等の心配はないからです。 これは gcc でコンパイルしたバイナリが特段問題ないのと同じことです。

OpenJDKにはさらにいくつかのバージョンがありますが、この記事では OpenJDK 8 または OpenJDK 11 を使います。 ほとんどのバージョンのGradleは OpenJDK 8 で動作します。 ただしGradle Ver7.0以降を使用する場合はOpenJDK 11以降が必要です。

ここではお使いのOS環境毎にどのOpenJDKをどこから入手すべきかの指針を示します。 尚、これらの情報はこの記事執筆時点での状況であり、時間の経過により変化することも有り得ることにご留意ください。

  • Windows XPの場合

  • Windows Vista以降の場合や64bit版Windowsの場合はまだ話は易しいのですが、 Windows XP以前や32bit版Windowsで使えるOpenJDKとなると話が込み入ってきます。

    幸いにも既にWindows XP用のJDK 8を持っている方は環境変数JAVA_HOMEの設定だけをすれば終了です。 今から新規にダウンロードするという方は、残念ながら少々面倒な手順を踏む必要があります。

    32bit用でありかつバイナリがSubsystem Version5.1以下のものを探す必要があります。 これに該当するものはhttps://jdk.java.net(OracleがOpenJDK用に別設したサイト)に見つけることができます (後述するAdoptiumが提供するバイナリは残念ながらWindows XPでは使えません)。 OpenJDK 8 についてはhttps://jdk.java.net/java-se-ri/8-MR3にダウンロード用コーナーが設置されています。 ただそのページの記載は少々分かりにくいのでWindows版のリンクを抜粋したものを以下に示します。このzipファイルをダウンロードしてください。

    https://download.java.net/openjdk/jdk8u41/ri/openjdk-8u41-b04-windows-i586-14_jan_2020.zip

    このURLは上記ダウンロードコーナーにおいて「Windows 10 i586 Java Development Kit (md5) 92 MB」と記述されたリンクの単なる抜粋です。
    このzipに含まれるバイナリの形式は32bit形式ですので32bit/64bit Windows上のどちらでも動作します (ただしSubsystem Version5.1のバイナリとなっていますので、動作させるにはWindowsXP以降が必要です)。

    ダウンロードしたアーカイブを適当なディレクトリで解凍します。 たとえば、E:\Android\OpenJDKというディレクトリをあらかじめ作っておき、そこへ openjdk-8u41-b04-windows-i586-14_jan_2020.zip をダウンロードし、 それを解凍するなどすればよいでしょう。 このとき E:\Android\OpenJDK 配下に java-se-8u41-ri という名前のディレクトリが作成されるはずです。

    次に環境変数JAVA_HOMEに、bin, include, lib が直下に存在するディレクトリのフルパスを設定してください。 これは通常、java-se-8u41-ri ディレクトリのフルパスに該当するはずです。 上記の例ですと 環境変数JAVA_HOME に E:\Android\OpenJDK\java-se-8u41-ri を設定します。 念のため E:\Android\OpenJDK\java-se-8u41-ri 直下に bin, include, lib が確かに存在することを確認してください。

    環境変数JAVA_HOMEはAndroid SDKやGradle等から参照されます。 Windowsの環境変数設定ダイアログから設定してもかまいませんが、src\make_apk\moai\setenv.batファイルをテキストエディタで開き、 JAVA_HOMEの値をそれに指定しておいてもよいでしょう。

    以上でOpenJDK 8のインストールは終了と言いたいところですが、このOracle版の OpenJDK 8 には問題があり、それを修正しなければ使用することができません。 そのままGradleで使った場合、以下のようなエラーが出てビルドが失敗してしまいます。

    java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty

    これはOpenJDK 8 内の jre\lib\security\cacerts ファイル(JKS:Java KeyStoreファイルと呼ばれるものですが)の中身に問題があることが原因です (証明書が何も登録されていません)。 Gradleはビルドの最中に必要なツールやライブラリをリポジトリからダウンロードしますが、その際にJavaのAPIを使ってHTTPS(SSL)による通信を行います。 JavaのAPIは、HTTPS(SSL)による通信時に上記のcacertsファイルを参照しますが、この内容に問題があるためそこでエラーとなるわけです。 代替の問題のない cacerts ファイルを入手し、それに差し替える必要があります。

    opensslコマンドがあれば、cacertsに証明書を登録するといったことも可能ですが、 今考えているのはWindows XP環境ですので大抵の場合はインストールされていないことでしょう。 そのようなツール一式、証明書一式を揃えるよりも代替の cacerts ファイルを入手した方が早いです。

    他のバージョンのOpenJDK内に含まれる cacerts ファイルでこの問題が生じないものが存在します。 そこで別途、他のバージョンのOpenJDKを別途ダウンロードし、そのzipを解凍し、その中に含まれるcacertsファイルだけを取り出します。 これを元のOpenJDK 8 内の jre\lib\security\cacerts ファイルへ上書きコピーすることでこの問題を修正できます。

    ならば最初から他のバージョンのOpenJDKを入手すればよいと思われるかもしれませんが、 残念ながら WindowsXP で動作させるにあたっては、現在、この OpenJDK 8 以外に適切なものがないのが実情です。 従いまして、WindowsXP の場合、あくまでベースはOpenJDK 8 でなんとかもっていくしかありません。

    具体的な修正手順
    さらに具体的な手順を示しましょう。 代替として OpenJDK 11 の cacerts ファイルを使うものとします (OpenJDK 11 の cacertsファイルならば問題が生じないことを確認しています)。

    まずOpenJDK 11 Windows x64版を別途入手します。 OpenJDK 11 Windows x64版 についてはhttps://jdk.java.net/java-se-ri/11にダウンロード用コーナーが設置されています。 ただそのページの記載は少々分かりにくいのでWindows版のリンクを抜粋したものを以下に示します。このzipファイルをダウンロードしてください。

    https://download.java.net/openjdk/jdk11/ri/openjdk-11+28_windows-x64_bin.zip

    このURLは上記ダウンロードコーナーにおいて「Windows/x64 Java Development Kit (sha256) 178.7 MB」と記述されたリンクの単なる抜粋です。
    このzipに含まれるバイナリの形式は64bit形式ですので64bit Windows上でのみ動作しますが、今回はこれを実際に使用するわけではありません。 用があるのはこの中にあるcacertsファイルのみです。 zipを展開すると、jdk-11\lib\security\cacerts というファイルがあるはずですが、このファイルを、 これを元のOpenJDK 8 内の jre\lib\security\cacerts ファイルへ上書きコピーします。

    この作業が終わったらこのOpenJDK 11 用のzipファイルや展開ディレクトリはもう不要ですのでまるごと削除して結構です。

    Close


    以上でOpenJDK 8のインストールは終了です。

    尚、Windows XPではOpenJDK 11のインストールは諦めた方が賢明でしょう(OpenJDK 8でも十分です)。

  • Windows Vista以降(32bit/64bit)の場合

  • 32bit用でありかつバイナリがSubsystem Version6.0以下のものを探す必要があります。 現在ネット上で入手できるOpenJDKのバイナリのほとんどはSubsystem Version6.0ですのでこの点に関してはあまり心配要りません。 おそらく現状、一番良い選択は Adoptium が配布しているOpenJDKかと思います (Oracleが配布しているOpenJDKバイナリではcacertsに関する不具合がありそれを修正する手間がかかりましたが、 このAdoptium版では問題はありません)。

    参考: Adoptiumとは?
    Wikipediaによれば、AdoptiumとはEclipse FoundationがOpenJDK用バイナリを提供するために立ち上げたプロジェクトで、公式サイトはhttps://adoptium.netです。 昔は AdoptOpenJDK という名前でしたが、Adoptiumに改名されたようです。 構成メンバーとして IBM、Microsoft、Red Hat などの有名所の名前も見受けられます。

    Close


    該当するWindows版のリンクを抜粋したものを以下に示します。このzipファイルをダウンロードしてください。

    https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u322-b06/OpenJDK8U-jdk_x86-32_windows_hotspot_8u322b06.zip

    現在、Adoptium は OpenJDK の zip ファイルの実体を adoptium.net 内ではなく github 内に置いていますが、このURLはそのzipファイルへのリンクの単なる抜粋です。
    https://adoptium.net において「Temurin 8 (LTS)」を選び、Operating Systemとして「Windows」、Architectureとして「x86」を選んでも 該当リンクへ到達することができます。

    このJDKに含まれるバイナリの形式は32bit形式ですので32bit/64bit Windows上のどちらでも動作します (ただしSubsystem Version6.0のバイナリとなっていますので、動作させるにはWindows Vista以降が必要です)。

    ダウンロードしたアーカイブを適当なディレクトリで解凍します。 たとえば、E:\Android\OpenJDKというディレクトリをあらかじめ作っておき、 そこへ OpenJDK8U-jdk_x86-32_windows_hotspot_8u322b06.zip をダウンロードし、それを解凍するなどすればよいでしょう。 このとき E:\Android\OpenJDK 配下に jdk8u322-b06 という名前のディレクトリが作成されるはずです。

    次に環境変数JAVA_HOMEに、bin, include, lib が直下に存在するディレクトリのフルパスを設定してください。 これは通常、jdk8u322-b06 ディレクトリのフルパスに該当するはずです。 上記の例ですと 環境変数JAVA_HOME に E:\Android\OpenJDK\jdk8u322-b06 を設定します。 念のため E:\Android\OpenJDK\jdk8u322-b06 直下に bin, include, lib が確かに存在することを確認してください。

    環境変数JAVA_HOMEはAndroid SDKやGradle等から参照されます。 Windowsの環境変数設定ダイアログから設定してもかまいませんが、src\make_apk\moai\setenv.batファイルをテキストエディタで開き、 JAVA_HOMEの値をそれに指定しておいてもよいでしょう。

    以上でOpenJDK 8のインストールは終了です。

    参考: OpenJDK 11
    あまりお勧めしませんが、何かの理由で後述するAndroid Gradle PluginでVer7.0以降を使用されたい場合は、 OpenJDK 8 ではなく OpenJDK 11 を使う必要があります。 その場合は、上記のzipファイルの替わりに以下をダウンロードする必要があります。

    https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.14.1%2B1/OpenJDK11U-jdk_x86-32_windows_hotspot_11.0.14.1_1.zip

    https://adoptium.net において「Temurin 11 (LTS)」を選び、Operating Systemとして「Windows」、Architectureとして「x86」を選んでも 該当リンクへ到達することができます。

    Close


  • Linux(64bit)の場合

  • Linux版であれば各ディストリビューション毎に用意されているパッケージ管理システムでOpenJDKをインストールすることもできるのかもしれません (ただしインストールされるバージョンが 8 や 11 であるという保証はありませんが)。

    それに頼らず自力でインストールするならば、おそらく現状、一番良い選択は Adoptium が配布しているOpenJDKかと思います (Oracleが配布しているOpenJDKバイナリではcacertsに関する不具合がありそれを修正する手間がかかりましたが、 このAdoptium版では問題はありません)。

    該当するLinux版のリンクを抜粋したものを以下に示します。このzipファイルをダウンロードしてください。

    https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u322-b06/OpenJDK8U-jdk_x64_linux_hotspot_8u322b06.tar.gz

    現在、Adoptium は OpenJDK の zip ファイルの実体を adoptium.net 内ではなく github 内に置いていますが、このURLはそのzipファイルへのリンクの単なる抜粋です。
    https://adoptium.net において「Temurin 8 (LTS)」を選び、Operating Systemとして「Linux」、Architectureとして「x64」を選んでも 該当リンクへ到達することができます。

    このJDKに含まれるバイナリの形式は64bit形式ですので64bit Linux上でのみ動作します (残念ながらAdoptiumではLinux x86版のバイナリの配布は行っていないようです)。

    ダウンロードしたアーカイブを適当なディレクトリで解凍します。 たとえば、ホームディレクトリ配下にDownloads/Android/OpenJDKというディレクトリをあらかじめ作っておき、 ダウンロードした OpenJDK8U-jdk_x64_linux_hotspot_8u322b06.tar.gz をその中へ移動し、それを解凍するなどすればよいでしょう。 以下のコマンドで解凍できるはずです。

    tar xvf OpenJDK8U-jdk_x64_linux_hotspot_8u322b06.tar.gz
    

    またこのとき ~/Downloads/Android/OpenJDK 配下に jdk8u322-b06 という名前のディレクトリが作成されるはずです。

    次に環境変数JAVA_HOMEに、bin, include, lib が直下に存在するディレクトリのフルパスを設定してください。 これは通常、jdk8u322-b06 ディレクトリのフルパスに該当するはずです。 上記の例ですと 環境変数JAVA_HOME に $HOME/Downloads/Android/OpenJDK/jdk8u322-b06 を設定します。 即ち以下のようなコマンドを .bashrc または src/make_apk/moai/setenv.sh 内に記述する形になります。

    export JAVA_HOME="$HOME/Downloads/Android/OpenJDK/jdk8u322-b06"
    

    念のため $HOME/Downloads/Android/OpenJDK/jdk8u322-b06 直下に bin, include, lib が確かに存在することを確認してください。

    環境変数JAVA_HOMEはAndroid SDKやGradle等から参照されます。

    以上でOpenJDK 8のインストールは終了です。

    参考: OpenJDK 11
    あまりお勧めしませんが、何かの理由で後述するAndroid Gradle PluginでVer7.0以降を使用されたい場合は、 OpenJDK 8 ではなく OpenJDK 11 を使う必要があります。 その場合は、上記のzipファイルの替わりに以下をダウンロードする必要があります。

    https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.14.1%2B1/OpenJDK11U-jdk_x64_linux_hotspot_11.0.14.1_1.tar.gz

    https://adoptium.net において「Temurin 11 (LTS)」を選び、Operating Systemとして「Linux」、Architectureとして「x64」を選んでも 該当リンクへ到達することができます。

    Close
これでOpenJDKのインストールは終了となります。 次の「参考: Gradleを手動でインストールする方法」へお進みください。

Close


参考: Gradleを手動でインストールする方法
OpenJDKのインストールが終わったら、 次にGradleと呼ばれるツールをインストールします。

Gradle は Androidが apk を生成するために現在正式に採用しているツールです。

極大雑把に言えばmakeに大幅に機能をつけたものといった感じのもので、ビルドに必要なツールやライブラリがPC上の存在しない場合は、 それをあらかじめ指定したリポジトリから自動的にダウンロードし、ビルドを行うものとなっています。 本来はAndroid専用のツールというわけではありませんが、Googleが作成したAndroid用のGradle プラグイン(Android Gradle Plugin、略してAGPと呼ばれます)やAndroid SDK BuildTools(以下単に SDK BuildTools と呼ぶことがあります)、OpenJDKを組み合わせて使うことで、 java ソースのコンパイル、apkファイルの作成までを一括で行います。

これはすべてが問題なく進む場合にはありがたいですが、しかし逆になんらかの問題が発生した場合、 原因分析しにくいといったデメリットもあります。 特にOpenJDK、AGPとのバージョンの整合性に関して問題は頻繁に起こりがちで、個人的には出来れば使用を避けたいツールですが、 残念ながら apk 作成に当たっては避けては通れません。

一番の問題は、何も考えず最新のGradleさえインストールして使えばいいといった状況ではないことです。 というのも、Gradleと連動して使用するいくつかのプラグインやツールが、Gradleのバージョンと密接に関係しているからです。 それぞれのバージョンの組み合わせをどのようにすべきかを慎重に見極める必要があり、 誤ったバージョンの組み合わせで使うと容易にエラーが発生し、ビルドに失敗してしまいます。 Gradleそのものを動かすのに必要なJavaのバージョンも考慮に入れなければなりません。

また見過ごされがちですが、これらの一連のツールが果たして今使っているOS上でエラーが発生することなくきちんと連携して動作するのかも問題になります。 (Javaは本来このような悩みを解消するための言語だったと思いますが、今回扱うツール/ライブラリにおいては残念ながらOS依存のエラーが発生してしまいます)。

このようなバージョンの組み合わせに関する難解さを解消するために、Moaiのソースコードには、今使っているOSも考慮に入れつつ適切なバージョンの組み合わせを 自動的にダウンロードしてインストールするinstall_sdkスクリプトが付属しています。通常はこのスクリプトを使うことを強くお勧めしますが、 ここではなんらかの理由でそれが使えない場合等に備え、手動でインストールを行う方法を述べます。

すべてのバージョンのGradleは一応、Gradle公式サイト Releases のコーナーからダウンロードできるようにはなっています。 これは以下のURLになります。

https://gradle.org/releases

しかし上述したように残念ながら自由気ままに好きなバージョンを選んでインストールすればよいといった状況ではありません。 ご使用のOS毎にどのバージョンを選ぶべきかの指針を以下に示します。

  • Windowsの場合

  • まずgradleをインストールするディレクトリを決めてください。 これはどこでも構いませんが、ここでの説明では、例えば「E:\Android\gradle」としておきましょう。

    次に環境変数GRADLE_USER_HOMEの値として、今決めたディレクトリのパスを指定します。 今回の例では、「E:\Android\gradle」を指定します。

    ここまではinstall_sdkスクリプトを使う場合と同じです。

    次に src\make_apk\moaiへ移動し、config_gen.bat をダブルクリックして実行します。 config_gen.bat の実行により、build.gradle等必要な設定ファイルが自動的に生成されます。

    手動といいながら結局ここでまた別のスクリプトを実行させるわけですが、 これを使わない場合、もはやbuild.gradleファイルなど必要な設定ファイルを自力で適切に記述する必要があり、 この記事の想定レベルをはるかに超えてしまいます。

    WindowsXP/Vistaでは AGP(Android Gradle Plugin)のバージョンとして2.3.0を使うようにbuild.gradleファイルに記述する必要があります。 それより新しいAGPでは、SDK BuildTools 内における AATP2 と呼ばれるプログラムの起動に失敗する可能性があるため避けなければなりません。

    一方、Windows 7以降では AGPのバージョンとして3.1.0あたりを使うのが最もよいでしょう (2.3.0でも動作しますが、そちらの方がビルドにかなり時間がかかります)。

    このように環境により発現するエラーなども存在し、build.gradleを自力で適切に記述するのはかなり骨が折れる作業になります。

    ここからはWindows XP/Vista と Windows7以降で手順が異なります。

    • Windows XP/Vistaの場合

    • Gradleのバージョンとして4.10.3を選びましょう (これより大きいバージョンや小さいバージョンでは、(ある程度の許容範囲はありますが)エラーが発生する確率が高くなり、お勧めできません)。

      まず gradle-4.10.3-bin.zip(約74MB) をダウンロードします(4.10.3の部分がGradleのバージョン番号です)。

      次に、環境変数GRADLE_USER_HOMEで指定したディレクトリ(以下単にGRADLE_USER_HOMEディレクトリと呼びます)の直下にダウンロードしたgradle-4.10.3-bin.zip を移動し、解凍してください。 このとき GRADLE_USER_HOMEディレクトリ直下に gradle-4.10.3 ディレクトリが存在するようにしてください。 今回の例ですと、E:\Android\gradle\gradle-4.10.3 という構成となります。

      解凍ツールによっては、gradle-4.10.3-bin\gradle-4.10.3 というように展開されるかもしれません。 この場合、中身にあるgradle-4.10.3 ディレクトリを一つ上の階層へ移動してください。 空になったgradle-4.10.3-binディレクトリの方は削除してかまいません。

      念のため GRADLE_USER_HOME\gradle-4.10.3 ディレクトリ直下に、bin, lib, LICENSEが確かに存在する状態になっていることを確認してください。 今回の例ですと、E:\Android\gradle\gradle-4.10.3 直下にそれらが存在する構成となります。

      今回の例の場合、環境変数GRADLE_USER_HOMEに指定すべき値はgradle-4.10.3 ディレクトリのパスではなく、 その一つ上のgradleディレクトリまでのパスであることにも注意してください。

      これでinstall_sdkスクリプトが自動的に行う処理を、手動で再現したことになります。

    • Windows 7以降の場合

    • Gradleのバージョンとして5.6.4を選びましょう (これより大きいバージョンや小さいバージョンでは、(ある程度の許容範囲はありますが)エラーが発生する確率が高くなり、お勧めできません)。

      まず gradle-5.6.4-bin.zip(約74MB) をダウンロードします(5.6.4の部分がGradleのバージョン番号です)。

      次に、環境変数GRADLE_USER_HOMEで指定したディレクトリ(以下単にGRADLE_USER_HOMEディレクトリと呼びます)の直下にダウンロードしたgradle-5.6.4-bin.zip を移動し、解凍してください。 このとき GRADLE_USER_HOMEディレクトリ直下に gradle-5.6.4 ディレクトリが存在するようにしてください。 今回の例ですと、E:\Android\gradle\gradle-5.6.4 という構成となります。

      解凍ツールによっては、gradle-5.6.4-bin\gradle-5.6.4 というように展開されるかもしれません。 この場合、中身にあるgradle-5.6.4 ディレクトリを一つ上の階層へ移動してください。 空になったgradle-5.6.4-binディレクトリの方は削除してかまいません。

      念のため GRADLE_USER_HOME\gradle-5.6.4 ディレクトリ直下に、bin, lib, LICENSEが確かに存在する状態になっていることを確認してください。 今回の例ですと、E:\Android\gradle\gradle-5.6.4 直下にそれらが存在する構成となります。

      今回の例の場合、環境変数GRADLE_USER_HOMEに指定すべき値はgradle-5.6.4 ディレクトリのパスではなく、 その一つ上のgradleディレクトリまでのパスであることにも注意してください。

      これでinstall_sdkスクリプトが自動的に行う処理を、手動で再現したことになります。


  • Linuxの場合

  • まずgradleをインストールするディレクトリを決めてください。 これはどこでも構いませんが、ここでの説明では、例えばホームディレクトリ直下にDownloads/Android/gradleというディレクトリを作り、 そこにインストールするとしましょう。

    次に環境変数GRADLE_USER_HOMEの値として、今決めたディレクトリのパスを指定します。 今回の例では、「$HOME/Downloads/Android/gradle」を値として指定します。

    ここまではinstall_sdkスクリプトを使う場合と同じです。

    次に cd src/make_apk/moaiへ移動し、config_gen.sh を実行します。 以下のようなコマンドを実行する形になります。

    cd src/make_apk/moai
    sh config_gen.sh
    

    config_gen.sh の実行により、build.gradle等必要な設定ファイルが自動的に生成されます。

    手動といいながら結局ここでまた別のスクリプトを実行させるわけですが、 これを使わない場合、もはやbuild.gradleファイルなど必要な設定ファイルを自力で適切に記述する必要があり、 この記事の想定レベルをはるかに超えてしまいます。

    Gradleのバージョンとして5.6.4を選びましょう (これより大きいバージョンや小さいバージョンでは、(ある程度の許容範囲はありますが)エラーが発生する確率が高くなり、お勧めできません)。

    まず gradle-5.6.4-bin.zip(約89MB) をダウンロードします(5.6.4の部分がGradleのバージョン番号です)。

    次に、環境変数GRADLE_USER_HOMEで指定したディレクトリ(以下単にGRADLE_USER_HOMEディレクトリと呼びます)の直下にダウンロードしたgradle-5.6.4-bin.zip を移動し、解凍してください。 このとき GRADLE_USER_HOMEディレクトリ直下に gradle-5.6.4 ディレクトリが存在するようにしてください。 今回の例ですと、~/Downloads/Android/gradle/gradle-5.6.4 という構成となります。

    今仮に、gradle-5.6.4-bin.zip を~/Downloads 直下へダウンロードし、7z コマンドを使用するものとしますと、 以下のような流れになるでしょう (入力の際、キーボードのTabキーを(適宜何度か)押して補完しながら打つと非常に楽になります)。

    cd ~/Downloads
    mkdir -p Android/gradle
    mv gradle-5.6.4-bin.zip Android/gradle/
    cd Android/gradle
    7z x gradle-5.6.4-bin.zip
    

    解凍ツールによっては、gradle-5.6.4-bin/gradle-5.6.4 というように展開されるかもしれません。 この場合、中身にあるgradle-5.6.4 ディレクトリを一つ上の階層へ移動してください。 空になったgradle-5.6.4-binディレクトリの方は削除してかまいません。

    念のため GRADLE_USER_HOME/gradle-5.6.4 ディレクトリ直下に、bin, lib, LICENSEが確かに存在する状態になっていることを確認してください。 今回の例ですと、~/Downloads/Android/gradle/gradle-5.6.4 直下にそれらが存在する構成となります。

    今回の例の場合、環境変数GRADLE_USER_HOMEに指定すべき値はgradle-5.6.4 ディレクトリのパスではなく、 その一つ上のgradleディレクトリまでのパスであることにも注意してください。

    これでMoai付属のスクリプトが自動的に行う処理を、手動で再現したことになります。

Gradleのディレクトリ配置をこのように取り決めた理由は、複数のバージョンのGradleをインストールした場合にどのバージョンのGradleを使うかを簡単に切り替えられるようにするためです。 またGradle関係のファイルをすべてGRADLE_USER_HOME配下に置いて管理しやすくするためでもあります。

これでGradleのインストールは終了となります。 次の「参考: Android SDK(Command line tools only)を手動でインストールする方法」へお進みください。

Close


参考: 環境変数GRADLE_USER_HOME
Gradleはデフォルトではホームディレクトリやユーザディレクトリ直下に「.gradle」という名前のディレクトリを作り、 その中に大量のキャッシュなどを生成します。 環境変数GRADLE_USER_HOMEを明示的に指定することにより「.gradle」ディレクトリが作られるのを抑制することができます。 その替わり、環境変数GRADLE_USER_HOMEで指定したディレクトリ配下にキャッシュなどが作成されるようになります。

おそらくこの方が、GRADLE_USER_HOMEディレクトリ一つですべてを一元管理できて便利です。 Gradleのアンインストールにおいても GRADLE_USER_HOMEディレクトリを丸ごと消去するだけでよく、 また同時にGradleが作成したキャッシュファイルなども完全に消去することができます。

Close


参考: Android SDK(Command line tools only)を手動でインストールする方法
Gradleのインストールが終わったら、 次にAndroid SDKの一部であるCommand line tools onlyと呼ばれるものをインストールします。

そもそもAndroid SDK とはandroid アプリを生成するための(主にjavaのための)ツール、ライブラリです。

Android SDK をインストールする方法はいくつかあります。 一般的には Android Studio をダウンロードするとこれが付属しますが、この記事ではAndroid Studioではなく(Android Studioと同じくGoogleが提供する) Command line tools only を使用します。 その方がファイル容量の大幅な節約が期待できるからです。

以下の手順でこれを行ってください。
  • Windowsの場合

  • まずAndroid SDKをインストールするディレクトリを決めてください。 これはどこでも構いませんが、ここでの説明では、例えば「E:\Android\sdk_root」としておきましょう。

    次に環境変数ANDROID_SDK_ROOTの値として、今決めたディレクトリのパスを指定します。 今回の例では、「E:\Android\sdk_root」を指定します。

    ここまではinstall_sdkスクリプトを使う場合と同じです。 この記事では、以降このディレクトリを単に ANDROID_SDK_ROOT と表記する場合があります。

    https://developer.android.com/studio へアクセスします。 Android Studioのでかでかとした宣伝画像を無視して一番下へスクロールします。

    Command line tools onlyと書かれた部分がみつかると思います。 Windowsの場合、commandlinetools-win-*_latest.zip (*はバージョン番号)と書かれたものをダウンロードします。 一応この記事執筆時点でのリンクを以下に示しておきますが、新しいバージョンが更新されるなどしてこのリンクが無効になる可能性があります。 リンクが無効になっている場合は上記のサイトから直接入手してください。

    https://dl.google.com/android/repository/commandlinetools-win-8092744_latest.zip(114MB)

    ダウンロードしたzipを展開しますと、cmdline-tools というディレクトリが作られているはずです。 これを最初に作成しておいた ANDROID_SDK_ROOT ディレクトリへ移動させます(ANDROID_SDK_ROOT/cmdline-tools という配置にします)。

    最後にsrc\make_apk\moai\compressive_configディレクトリへ移動し、sdk_setup.bat を実行します。

    sdk_setup.batでは以下を行っています。
    1. config_in\package.xml を ANDROID_SDK_ROOT\tools 直下へコピー
    2. config_in\yes.bat をANDROID_SDK_ROOT\cmdline-tools\bin 直下へコピー
    3. ANDROID_SDK_ROOT\sdkmanagar.bat を実行(ライセンスファイルなどのセットアップを行っています)

  • Linuxの場合

  • まずAndroid SDKをインストールするディレクトリを決めてください。 これはどこでも構いませんが、ここでの説明では、例えばホームディレクトリ直下にDownloads/Android/sdk_rootというディレクトリを作り、 そこにインストールするとしましょう。

    次に環境変数ANDROID_SDK_ROOTの値として、今決めたディレクトリのパスを指定します。 今回の例では、「$HOME/Downloads/Android/sdk_root」を値として指定します。

    ここまではinstall_sdkスクリプトを使う場合と同じです。 この記事では、以降このディレクトリを単に ANDROID_SDK_ROOT と表記する場合があります。

    https://developer.android.com/studio へアクセスします。 Android Studioのでかでかとした宣伝画像を無視して一番下へスクロールします。

    Command line tools onlyと書かれた部分がみつかると思います。 Linuxの場合、commandlinetools-linux-*_latest.zip (*はバージョン番号)と書かれたものをダウンロードします。 一応この記事執筆時点でのリンクを以下に示しておきますが、新しいバージョンが更新されるなどしてこのリンクが無効になる可能性があります。 リンクが無効になっている場合は上記のサイトから直接入手してください。

    https://dl.google.com/android/repository/commandlinetools-linux-8092744_latest.zip(114MB)

    ダウンロードしたzipを展開しますと、cmdline-tools というディレクトリが作られているはずです。 これを最初に作成しておいた ANDROID_SDK_ROOT ディレクトリへ移動させます(ANDROID_SDK_ROOT/cmdline-tools という配置にします)。

    最後にsrc/make_apk/moai/compressive_configディレクトリへ移動し、sdk_setup.sh を実行します。

    cd src/make_apk/moai/compressive_config
    sh sdk_setup.sh
    

    sdk_setup.shでは以下を行っています。
    1. config_in/package.xml を ANDROID_SDK_ROOT/tools 直下へコピー
    2. ANDROID_SDK_ROOT/sdkmanagar を実行(ライセンスファイルなどのセットアップを行っています)

これでCommand line tools onlyのインストールとその基本的な初期化は終わりました。 次の「参考: Android SDK(実体)を手動でインストールする方法」へお進みください。

Close


参考: Android SDK(実体)を手動でインストールする方法
Command line tools only のインストールと初期化が終わったら、 次にAndroid SDKの実体をインストールします。

Gradleを使ってAndroid SDKの実体をインストールすることもできますが、Gradleによるダウンロードは残念ながらあまり当てにならない場合があるため、 ここでは、Command line tools only に付属している sdkmanager を使ってAndroid SDKの実体をインストールする方法を述べます。

sdkmanager コマンドの実行に伴って、--sdk_rootの指定は必須と考えてよいでしょう。 この値は、(カレントディレクトリからの)sdk_rootディレクトリの相対位置です。

  • 準備

  • まずコマンドライン上でsdkmanagarが存在するディレクトリまで移動しておきます。

    • Windowsの場合

    • コマンドプロンプトを開き、cd コマンドで ANDROID_SDK_ROOT\cmdline-tools\binディレクトリへ移動しておく必要があります。 あるいは ANDROID_SDK_ROOT\cmdline-toolsディレクトリまでエクスプローラ上で移動し、binフォルダをShiftキーを押しながら右クリックし、 「コマンドウィンドウをここから開く」を選択してもよいです。

    • Linuxの場合

    • cd コマンドで ANDROID_SDK_ROOT/cmdline-tools/binディレクトリへ移動しておく必要があります。

  • platforms sdk

  • これはいわゆるplatforms APIと呼ばれるもので、Javaでコーディングする際にimportして使うためのライブラリとなります。 Moaiは現時点ではAPIレベル24と呼ばれるものを使用しています。

    • Windowsの場合

    • コマンドプロンプト上で ANDROID_SDK_ROOT\cmdline-tools\binディレクトリへ移動した状態で、以下を実行します。

      sdkmanager.bat --sdk_root=../../ "platforms;android-24"
      

    • Linuxの場合

    • ANDROID_SDK_ROOT/cmdline-tools/binディレクトリへ移動した状態で以下を実行します。

      sh sdkmanager --sdk_root=../../ "platforms;android-24"
      

    これによりANDROID_SDK_ROOT 直下にplatforms、さらにその直下にandroid-24ディレクトリが自動的に作成され、 中身がダウンロードされます(およそ130MB弱)。

  • platforms-tools

  • platform-toolsには adb などのコマンドが含まれています。 adbは、実機とUSBで繋いで apk をインストールしたりデバッグしたりすることができるコマンドです。

    • Windowsの場合

    • コマンドプロンプト上で ANDROID_SDK_ROOT\cmdline-tools\binディレクトリへ移動した状態で、以下を実行します。

      sdkmanager.bat --sdk_root=../../ "platform-tools"
      

    • Linuxの場合

    • ANDROID_SDK_ROOT/cmdline-tools/binディレクトリへ移動した状態で以下を実行します。

      sh sdkmanager --sdk_root=../../ "platform-tools"
      

    これによりANDROID_SDK_ROOT 直下にplatform-toolsディレクトリが自動的に作成され、中身がダウンロードされます(およそ30MB)。

    注意: Windows版platform-toolsについて
    platform-tools内に含まれるWindows用バイナリに関してですが、これらは api-ms-win-crt-*.dll といったdllを動的リンクするものがあります。 そのためシステムにこれが存在しない古いWindowsの場合、例えば adbコマンドなどの起動に失敗します。 その場合はMicrosoft Visual C++ 2015 Redistributable Update 3 RCをインストールしておきましょう。

    TortoiseSVN x64版などをインストールしている場合、TortoiseSVN/binディレクトリ内にapi-ms-win-crt-*.dllが含まれています。 さらにTortoiseSVNはインストール時、このディレクトリを環境変数PATHへ追加します。 これによりplatform-tools内のコマンドを起動するとDLLの動的リンクに不整合が生じ、起動に失敗することがあります。 TortoiseSVNをインストールされている方は、この点に注意してください。 platform-tools内のコマンドを使う直前に環境変数PATHを設定しなおすなどの配慮が必要になるでしょう。
    現在のplatform-toolsに含まれるバイナリは、Subsystem Version 6.0 のものとなっていますので 実行させるためにはWindows Vista以降が必要です。 ただし単にapkを生成するだけならば、これらのバイナリの実行は必須ではありません (古いバージョンのplatform-toolsであれば、Subsystem Version 5.1 のバイナリとなっているものも確かあったと思います。 それならばWindows XPでもこれらを使用できるでしょう)。

    Close


  • build-tools

  • これはAGP(Android Gradle Plugin)などと連動して使われ、apkを作成するためのツールなどが含まれます。 build-toolsのバージョンは28.0.3を指定してください。 (指定すべきbuild-toolsのバージョンはAGPのバージョンに応じても変わりますが、ここまでのインストールで特に変な設定をしていない限り とりあえずここは28.0.3で問題ありません)。

    • Windowsの場合

    • コマンドプロンプト上で ANDROID_SDK_ROOT\cmdline-tools\binディレクトリへ移動した状態で、以下を実行します。

      sdkmanager.bat --sdk_root=../../ "build-tools;28.0.3"
      

    • Linuxの場合

    • ANDROID_SDK_ROOT/cmdline-tools/binディレクトリへ移動した状態で以下を実行します。

      sh sdkmanager --sdk_root=../../ "build-tools;28.0.3"
      

    これによりANDROID_SDK_ROOT 直下にplatform-toolsディレクトリが自動的に作成され、中身がダウンロードされます(およそ130MB)。

長かったですがここまでで一応手動のインストールは終わりになり、install_sdkスクリプトが行う処理をすべて再現したことになります。 次の「APKの生成」にお進みください。

Close


ただしこれらのツール/ライブラリを手動で適切にインストールするのはかなり難しいと思います。 また、インストール中にエラーなどが発生した場合、設定を変えて再度試行するなどといった作業が 手動では恐ろしく面倒になる可能性があります。

APKの生成


ここから先はMoaiのソースコードにあるスクリプトおよびGradleを使って、最終的なapkファイルを作成します。 以下の手順でスクリプトを実行させてください。

  • Windowsの場合
    1. src/make_apk/moaiへ移動し、make_assets.bat をダブルクリックして実行します。

    2. これはandroid-v2.*-{MACHINE}ディレクトリからネイティブバイナリ以外のすべての必要なファイルを src/make_apk/moai/assets_draft/common配下へコピーし、それを元にsrc/make_apk/moai/assets_draft/common.zipを作ります。

      またandroid-v2.*-{MACHINE}ディレクトリからネイティブバイナリを src/make_apk/moai/assets_draft/bin配下へコピーし、それを元にsrc/make_apk/moai/assets_draft/bin.zipを作ります。

      最後にsrc/make_apk/moai/assets_draft/common.zip、src/make_apk/moai/assets_draft/bin.zip を src/make_apk/moai/app/src/main/assets 配下へコピーします。 これらのzipファイルは最終的にapkファイルに取り込まれます。

    3. src/make_apk/moaiへ移動し、make_apk.bat をダブルクリックして実行します。

    4. これは Gradle を使って残りの処理のすべてを行います。

      まず初回のGradleの起動においては、Android Gradle Plugin がまだPC上にインストールされていないため、 GradleはこれをGoogle等のリポジトリから自動的にダウンロードしてインストールします。 このような処理が入るため、これにはかなり時間がかかります。 ただし二回目以降のGradleの起動ではこの処理はスキップできるはずです。

      次にGradleは、src/make_apk/moai/app/src/main/jniLibs 配下に置かれたネイティブライブラリ(libZnk、libRrk, libRano, libMoai, libtsl-17)を apk内へ取り込みます。

      またGradleは src/make_apk/moai/app/src/main/java/znkproject/moai にある javaソースをコンパイルし、 生成したclassファイルをapk内へ取り込みます。

      上記がすべてエラーなく終了した場合、最後にsrc/make_apk/moai直下にmoai-v2.*-android.apkというファイルが生成され、 BUILD SUCCESS(成功)となります。 このapkファイルがこのセクションで最終的に作成したかったものとなります。 すべてが滞りなく成功していれば、現在Moaiの公式サイトでダウンロードできるapkと(タイムスタンプなどを除き)基本的に同じものが生成されているはずです。


  • Linuxの場合
    1. src/make_apk/moaiへ移動し、make_assets.sh を実行します。

    2. 以下のような流れになります。

      cd src/make_apk/moai
      sh make_assets.sh
      

      これはandroid-v2.*-{MACHINE}ディレクトリからネイティブバイナリ以外のすべての必要なファイルを src/make_apk/moai/assets_draft/common配下へコピーし、それを元にsrc/make_apk/moai/assets_draft/common.zipを作ります。

      またandroid-v2.*-{MACHINE}ディレクトリからネイティブバイナリを src/make_apk/moai/assets_draft/bin配下へコピーし、それを元にsrc/make_apk/moai/assets_draft/bin.zipを作ります。

      最後にsrc/make_apk/moai/assets_draft/common.zip、src/make_apk/moai/assets_draft/bin.zip を src/make_apk/moai/app/src/main/assets 配下へコピーします。 これらのzipファイルは最終的にapkファイルに取り込まれます。

    3. src/make_apk/moaiへ移動し、make_apk.sh を実行します。

    4. 以下のような流れになります。

      cd src/make_apk/moai
      sh make_apk.sh
      

      これは Gradle を使って残りの処理のすべてを行います。

      まず初回のGradleの起動においては、Android Gradle Plugin がまだPC上にインストールされていないため、 GradleはこれをGoogle等のリポジトリから自動的にダウンロードしてインストールします。 このような処理が入るため、これにはかなり時間がかかります。 ただし二回目以降のGradleの起動ではこの処理はスキップできるはずです。

      次にGradleは、src/make_apk/moai/app/src/main/jniLibs 配下に置かれたネイティブライブラリ(libZnk、libRrk, libRano, libMoai, libtsl-17)を apk内へ取り込みます。

      またGradleは src/make_apk/moai/app/src/main/java/znkproject/moai にある javaソースをコンパイルし、 生成したclassファイルをapk内へ取り込みます。

      上記がすべてエラーなく終了した場合、最後にsrc/make_apk/moai直下にmoai-v2.*-android.apkというファイルが生成され、 BUILD SUCCESS(成功)となります。 このapkファイルがこのセクションで最終的に作成したかったものとなります。 すべてが滞りなく成功していれば、現在Moaiの公式サイトでダウンロードできるapkと(タイムスタンプなどを除き)基本的に同じものが生成されているはずです。


トラブルシューティング


デフォルトの設定でinstall_sdkやmake_apkの実行で問題が発生する場合、念のためもう一度それらを実行してください。

2度目の実行でも同様の問題が発生する場合、使用するAGPのバージョンを変更することで問題が解決する可能性はあります。 Moaiではこのバージョンの組み合わせとしてデフォルト以外にも4つの予備パターンを用意し、このような状況に備えてあります。 デフォルトの組み合わせパターンがダメでも、その他4つのパターンのどれかをトライすれば問題が解決するかもしれません (まあ、運悪く全部失敗するシナリオもあり得ますが…)。

src/make_apk/moai/rrk_script/config_gen.rrks をテキストエディタで開きます。 8行目あたりの /* User Customize */ と書かれた行から、 24行目あたりの /* endof User Customize */ と書かれた行までの範囲を修正することで、使用するAGPのバージョンをカスタマイズできます。 この範囲だけを抜粋したものを以下に示します。

/*****************************************************************************/
/* User Customize */

app_abi = [
	"armeabi-v7a",
	"arm64-v8a",
	"x86",
	"x86_64",
	//"armeabi",
]

//agp_ver = AGP_e_2_2_0
//agp_ver = AGP_e_2_3_0
//agp_ver = AGP_e_3_1_0
//agp_ver = AGP_e_4_0_0
//agp_ver = AGP_e_7_0_0

/* endof User Customize */
/*****************************************************************************/

AGPのバージョンを変更するには agp_ver 変数に指定する値を変更します。 つまり「agp_ver =」とある文を実行させればよいのですが、ただしその前にコメントアウトについて知っておいた方がよいので説明します。 このファイルにおける「//」記号は、その位置から行末までをなかったことにします(この処理をコメントアウトと呼びます)。 また「/*」「*/」も同様に、それらで囲まれた間の部分をなかったことにします。 例えば下記のような記述があった場合、

//agp_ver = AGP_e_2_2_0
agp_ver = AGP_e_2_3_0 /* Windows XP OK */

これは実質以下のように書いたのと全く同じことです。

agp_ver = AGP_e_2_3_0

agp_verの値の切り替えを少しでも楽にするため、「//」の付けor外しだけでこれを行いましょう。 例えば修正前が以下のようであったとします。

//agp_ver = AGP_e_2_2_0
//agp_ver = AGP_e_2_3_0
//agp_ver = AGP_e_3_1_0
//agp_ver = AGP_e_4_0_0
//agp_ver = AGP_e_7_0_0

この状態ではすべてがコメントアウトされているため、agp_verの値はデフォルト値になっています。 ここで4行目の頭の「//」を削除すると、4行目だけが実行され、agp_ver の値としてAGP_e_4_0_0 を指定したのと同じになります。 修正後の様子を以下に示します。

//agp_ver = AGP_e_2_2_0
//agp_ver = AGP_e_2_3_0
//agp_ver = AGP_e_3_1_0
agp_ver = AGP_e_4_0_0
//agp_ver = AGP_e_7_0_0

さらに4行目の頭に「//」を付け、3行目の頭の「//」を削除すると、3行目だけが実行され、agp_ver の値としてAGP_e_3_1_0 を指定したのと同じになります。 修正後の様子を以下に示します。

//agp_ver = AGP_e_2_2_0
//agp_ver = AGP_e_2_3_0
agp_ver = AGP_e_3_1_0
//agp_ver = AGP_e_4_0_0
//agp_ver = AGP_e_7_0_0

このようにして指定の切り替えを行えるわけです。

「//」を外す行は上記のうちのどれか一つに限定しましょう。 「//」を外した行が複数存在する場合、一番下の行にある文の指定が反映されます。

参考: agp_ver のデフォルト値
Windows XP/Vistaの場合、デフォルトでは agp_ver に AGP_e_2_3_0 が設定されています。 Windows XP/Vistaにおいて agp_ver を AGP_e_2_3_0 より大きいバージョンにするとエラーが発生する可能性があるため、 それは避けた方がいいでしょう。

それ以外の環境(Windows 7以降やLinux)では、デフォルトでは agp_ver に AGP_e_3_1_0 が設定されています。


修正が完了したら config_gen.rrksを上書き保存してください。

src/make_apk/moai へ移動し、install_sdkを実行します。 Windowsの場合はinstall_sdk.batをダブルクリックする形になります。 Linuxの場合は下記のコマンドを実行する形になります。

sh install_sdk.sh

これにより build.gradle やその他の必要な設定ファイルの内容を更新できます。 また必要なバージョンのGradleが自動的にダウンロードされインストールされます。 場合によってはOpenJDKのバージョンを変える必要も生じますが、これも自動的にダウンロードされます。

config_gen.rrksの全ソースコード (28 lines)

import rrk_script/cfg_gen_impl

varia app_abi conststr[]
varia agp_ver AGPVer = CfgGen_getDefaultAGPVer()


/*****************************************************************************/
/* User Customize */

app_abi = [
	"armeabi-v7a",
	"arm64-v8a",
	"x86",
	"x86_64",
	//"armeabi",
]

//agp_ver = AGP_e_2_2_0
//agp_ver = AGP_e_2_3_0
//agp_ver = AGP_e_3_1_0
//agp_ver = AGP_e_4_0_0
//agp_ver = AGP_e_7_0_0

/* endof User Customize */
/*****************************************************************************/


return CfgGen_main( agp_ver, app_abi )
Close


参考: config_gen.rrks
AGP(Android Gradle Plugin)が対応するGradleやSDK Build Toolsのバージョンを調査する上でもっとも重要な情報源は、 Googleが提供するAndroid Developersのサイトの Android Gradle plugin release notes です。 以下のURLから参照できます。

https://developer.android.com/studio/releases/gradle-plugin

この情報を基に、バージョンの対応をすべて整合性を保ってbuild.gradleファイルを手動で記述し、 適切なバージョンのGradleとOpenJDKを使ってこれを実行するのははかなり「離れ業」となりますが、 config_gen.rrks スクリプトによって、この作業を大幅に軽減できます。

拡張子rrksはraraku言語のソースコードを意味します(もう一つrrkhとなっている拡張子もあり、こちらはraraku言語のヘッダファイルです)。 rarakuとは我々が独自で開発したプログラミング言語であり、Moaiのソースコード内ではデフォルトでrarakuが実行できる環境が整っています。

Close


agp_ver変数の詳細
config_gen.rrks 内のagp_ver変数に指定可能な全列挙型値とその意味について、詳細を以下に示します。 これらは Gradle, AGP, SDK Build Tools の各バージョン、そして指定すべきリポジトリなどの要素を矛盾なく組み合わせたものです。

  • AGP_e_2_2_0

  • AGP(Android Gradle Plugin) Ver2.2.0を使用します。 これは config_gen が提供するものの中では最もバージョンが古いものですが、apkを作る分には特に支障ありません。 なんらかの理由で、これより新しいバージョンが使用できない場合に選択するとよいでしょう。

    • OpenJDK:
    • OpenJDK 8が必須となります。OpenJDK 11 を使った場合はエラーが発生しビルドに失敗します。

    • SDK BuildTools:
    • サポートされる SDK BuildTools の最小バージョンは 23.0.2 となりますが、config_gen では 24.0.2 を自動的に使う形にしてあります。

    • Gradle:
    • Gradle 3.5.1(69MB) を自動的にダウンロードして使います。


    • Gradleが参照するリポジトリ:
    • jcenter()、mavenCentral() となります (このバージョンのGradleではリポジトリgoogle()の指定はサポートされていません)。

  • AGP_e_2_3_0

  • AGP(Android Gradle Plugin) Ver2.3.0を使用します。 若干古いバージョンになりますが、apkを作る分には全く支障ありません。 特に開発環境がWindows XP/Vistaの場合、このagp_verを選ぶとよいでしょう。 Windows XP/Vistaでは config_gen でのデフォルトはこれになっています。

    • OpenJDK:
    • OpenJDK 8が必須となります。OpenJDK 11 を使った場合はエラーが発生しビルドに失敗します。

      補足
      正確に言えば、Android Gradle Plugin Ver 3.0.0以下を指定した場合に OpenJDK 11 ではエラーが発生します。 3.1.0以降であれば OpenJDK 11 でもビルド可能であることを確認しています。


    • SDK BuildTools:
    • サポートされる SDK BuildTools の最小バージョンは 25.0.0 となりますが、config_gen では 28.0.3 を自動的に使う形にしてあります。

      参考: SDK Build Tools 28.0.3
      このagp_verでの SDK Build Toolsの最小バージョンは 25.0.0 となっておりますが、敢えて 28.0.3 という少し高めのバージョンをデフォルトとして設定しています (なんらかの理由で 28.0.3 が使用できない場合は、AGPのバージョンに応じてSDK Build Toolsのバージョンを下げたり、agp_ver = AGP_e_2_2_0(SDK Build Toolsのバージョンは24.0.2)を選ぶこともできます)。

      apkフォーマットには署名とよばれるファイルの正当性を検証する情報が存在し、SDK Build Toolsに含まれる apksigner というツールによって この署名を行うことができます。 しかしSDK Build Tools 28.0.0 より前のapksignerでは、一部のAPKの判定でフォーマットの識別に失敗する不具合があります。

      例えば 27.0.3以前のapksignerでは一部の正常なapkで ERROR: APK Signature Scheme v2 signer #1: Malformed additional attribute #1 といったようなエラーが表示されることがあります。

      SDK Build Tools 28.0.0 のapksigner においては、この不具合が修正されています。

      Close


    • Gradle:
    • Gradle 4.10.3(74MB) を自動的にダウンロードして使います。


    • Gradleが参照するリポジトリ:
    • mavenCentral(), google(), gradlePluginPortal() となります。 (Gradle Ver4.0よりリポジトリ google()が、Ver4.4よりリポジトリ gradlePluginPortal() の指定がそれぞれサポートされます。 またリポジトリ jcenter() からは新しいAGPがダウンロードできないため、このバージョン以降は、jcenter()は指定できません)。

  • AGP_e_3_1_0

  • AGP(Android Gradle Plugin) Ver3.1.0からVer3.6.4のうちいずれかを使用します。 古くもなく新しくもない中間くらいのバージョンです。
    Linux または Windows 7以降では、最もお勧めできるバージョンであり、config_gen でのデフォルトもこれにしてあります。

    Windows XP/Vista 上ではこのバージョン以上になると、「AATP2 の起動に失敗した」といった旨のエラーが出て Gradleの実行に失敗する可能性があります。 WindowsXP/Vista 上では AGP_e_2_3_0以下を選択するべきです。

    • OpenJDK:
    • OpenJDK 8 と OpenJDK 11 のいずれでも動作します。

    • SDK BuildTools:
    • サポートされる SDK BuildTools の最小バージョンは 28.0.3 となります。

    • Gradle:
    • Gradle 5.6.4(89MB) を自動的にダウンロードして使います。


    • Gradleが参照するリポジトリ:
    • mavenCentral(), google(), gradlePluginPortal() となります。

  • AGP_e_4_0_0

  • AGP(Android Gradle Plugin) Ver4.0.0からVer4.2.0のうちいずれかを使用します。 やや新しいバージョンです。

    • OpenJDK:
    • OpenJDK 8 と OpenJDK 11 のいずれでも動作します。

    • SDK BuildTools:
    • サポートされる SDK Build Tools の最小バージョンは 29.0.2 となります。

    • Gradle:
    • Gradle 6.9.2(103MB) を自動的にダウンロードして使います。


    • Gradleが参照するリポジトリ:
    • mavenCentral(), google(), gradlePluginPortal() となります。

  • AGP_e_7_0_0

  • AGP(Android Gradle Plugin) Ver7.0.0以降を使用します。 config_genがサポートするもの中では最新バージョンとなります。

    • OpenJDK:
    • OpenJDK 11が必須となります。OpenJDK 8 を使った場合はエラーが発生しビルドに失敗します。

      補足
      AGP Ver7.0.0以降ではGradle Ver7.0以降が必須となっています。 さらにGradle Ver7.0以降を動作させるためには、OpenJDK 11以降が必須となります。 そのため、ここではOpenJDK 11が必須ということになります。


    • SDK BuildTools:
    • サポートされる SDK Build Tools の最小バージョンは 30.0.2 となります。

    • Gradle:
    • Gradle 7.4(110MB) を自動的にダウンロードして使います。


    • Gradleが参照するリポジトリ:
    • mavenCentral(), google(), gradlePluginPortal() となります。

上記の対応を踏まえ、config_gen.rrks はインストールすべきバージョンの組み合わせを決定し、 config_in\config.batやconfig_in/config.sh を自動生成します。 そしてそれに記述された情報に則ってインストール作業を自動的に行うものが install_sdk となります。

実際の実装はさらにrrk_script/cfg_gen_impl.rrks 内に記述されていますが、これの中身の解説については この記事の想定レベルを超えるので割愛します。

Close


目次に戻る

Android実機へ転送する

このセクションでは今までのセクションで作成したzipやapkファイルをAndroid実機に転送する方法を説明します。 ただここに説明した方法以外であっても、Android実機に転送可能ならどんな方法でもよいです。

USBでスマホと接続し、Android側のディレクトリをPC側から参照することで転送


多くのスマホではPCとUSBで接続することで、PC側からスマホ内のディレクトリが見えるようになり、 そのディレクトリへzipやapkファイルをコピーすることができると思います。 機種によっては「設定」でこのようなファイル転送をするためのモードを選んでおく必要があるかもしれません。 たとえば FireHDの場合、「設定」⇒「接続デバイス」⇒「USB」から「ファイル転送」を選んでおく必要があります。

スマホにapkをコピーしたら、スマホ上でファイルマネージャアプリ(この名前は機種によって異なるかもしれません)を開き、 コピーしたapkのあるディレクトリへ移動し、そのapkをタップして開きましょう。 アプリインストール用のウィザードが表示されますのでその指示に従います。
apkのインストールについての詳細はAndroid APK版の入手およびインストール方法を参照してください。

ネットを経由


なんらかの理由でUSBが使えない場合は、ネットを経由させます。 zipやapkファイルを一旦適当なWebサイト上にアップし、実機からこれをダウンロードするという方法です。 回りくどい方法ですが確実ではあります。

参考: PC版のMoaiを使って転送する方法
ちょっと裏技チックではありますが PC版の Moai を使う手もあります (ちなみに筆者はこの方法もよく使います)。

MoaiはローカルWebサーバであり、PC上でこれを起動しておけばそのPCがそのまま小さなWebサイトのようなものになります (Moaiを使用するときブラウザからhttp://127.0.0.1:8124 でアクセスしますが、これがまさに今Moaiが起動しているPCの URLを意味しており、そのPCが小さなWebサイトとして機能しているということに他なりません)。

ただし、Moaiはデフォルトでは他IPからのアクセスを弾くようにセキュリティ設定されてあるため、まずこの設定を解除しておく必要はあります。 具体的な手順は以下のようになります。

  1. PC上でMoaiを起動し、同じPC上のブラウザから Moaiセキュリティ設定へアクセスする。

  2. Moaiセキュリティ設定の画面でacceptable_hostメニューをLOOPBACKからANYに変更する。

  3. Moaiセキュリティ設定の画面の下方にある「設定の確定」ボタンを押す。

  4. Moaiセキュリティ設定の画面のserver_nameで表示されているIPをメモしておく(これがこのPCのLAN上でのIPアドレスとなります)。

  5. Moaiセキュリティ設定の画面の一番下にある「Moaiを再起動」ボタンを押す。

この設定は最初の1回だけでかまいません。 以降のMoaiの起動からもこの設定は有効となります(さすがにMoaiを再インストールしたら元に戻ってしまいますが)。 もしも元の設定に戻したい場合は上記の手順でのANYをLOOPBACKに戻せばOKですが、家庭内のLAN内でMoaiを使う程度であればANYのままでも別に問題はないと思います。

次にPC版のMoaiのトップディレクトリ内にあるdoc_rootディレクトリ直下に zipやapkファイルを置いておきます。 後はスマホ側のブラウザから、メモしておいた(LAN上における)IPアドレスへアクセスするだけです。

例えばそのIPが 192.168.1.10 であり、doc_rootディレクトリ直下へ置いた zip ファイルが mode-v2.3-android-armeabi-v7a.zip という名前だったとしましょう。 その場合スマホ側のブラウザから、http://192.168.1.10:8124/moai-v2.3-android-armeabi-v7a.zip へアクセスし、これをダウンロードします (スマホからこのURLを打ち込むのは若干大変かもしれませんが…)。

あるいは理屈は全く同じことですが、Moai以外のWebサーバアプリ(例えばApacheなど)をPC側で起動しても 同様のことができます。

Close


スマホにapkをダウンロードしたら、スマホ上でファイルマネージャアプリ(この名前は機種によって異なるかもしれません)を開き、 ダウンロードしたapkのあるディレクトリ(大抵はDownloadディレクトリです)へ移動し、そのapkをタップして開きましょう。 アプリインストール用のウィザードが表示されますのでその指示に従います。
apkのインストールについての詳細はAndroid APK版の入手およびインストール方法を参照してください。

USBでスマホと接続し、adbコマンドにより直接インストールする(開発者向け)


この方法はうまく設定さえ成功すれば最も高速で強力な方法となります。 スマホにapkを転送するだけでなく、インストールまで行えてしまうからです。

ただし若干設定が難しい上、Android SDK内のplatforms-toolsやGoogle USB Driverのインストールまで必要になり、 既に挙げた二つの方法より初期設定がはるかに手間ですので一般ユーザにはお勧めできない方法です。 ソースコードを修正して実機へのインストールというルーチンを頻繁に行うAndroidアプリの開発者向けの方法といえるでしょう。

機種によっては adb コマンドの使用を可能とするための下準備として、スマホ側の「設定」を調整しておく必要があるかもしれません。 たとえば FireHDの場合、以下の設定が必要です。

  • 「設定」⇒「端末オプション」⇒「開発者オプション」から「USBデバッグ」をONにしておく。

  • ただし「開発者オプション」の項目はデフォルトでは無効になっています。 これを有効にさせるには「ビルド番号」を7回ほど連続でタップします。 「ビルド番号」は「設定」⇒「端末オプション」⇒「Fireタブレットのバージョン情報」⇒「ビルド番号」にあります。

    機種によっては「設定」⇒「端末オプション」⇒「ソフトウェア情報」⇒「ビルド番号」になっていることもあります。 また機種によっては、以下のように名前が異なることもあります。
    • 「開発者オプション」
    • 「開発者モード」となっていることがあります。

    • 「ビルド番号」
    • 「シリアル番号」となっていることがあります。

    • 「USBデバッグ」
    • 「ADBを有効にする」となっていることがあります。

  • 「設定」⇒「接続デバイス」⇒「USB」から「ファイル転送」を選んで置く。

  • ただし機種によってはこれは必要ないかもしれません。

Moaiでは apk の作成が成功した時点で、Android SDK内にplatforms-toolsがもうインストールされているはずです。 adbコマンドはこの中にあります。 このadbコマンドを直接実行させてもよいですが、Moaiのソースコードにはadbコマンドを若干使いやすくしたスクリプトが src/make_apk/moai/adb 内に用意されているのでそれを使うとよいでしょう。 これを使う手順は以下のようになります。

  • Windowsの場合

  • Windowsで adb コマンドを使う場合、まず「Google USB Driver」(Googleが提供するadb用ドライバ)をWindowsにインストールしておく必要があります。

    名前は「Google USB Driver」となっていますが、あくまでUSBを繋いでadbコマンドを使う場合のみ必要となります。 単にUSBでPCとスマホと接続し、Android側のディレクトリをPC側から参照したりファイルを転送したいだけの場合、 このドライバをインストールする必要はありません。
    FireHD で adbコマンドを使う場合のWindows用ドライバとして、Amazonが kindle_fire_usb_driver というものを配布しています。 しかしこれはうまくインストールできない場合があるため、避けた方が無難です。 FireHDであっても「Google USB Driver」をインストールすることをお勧めします。

    これのインストールがまだの場合は、以下の「Google USB Driverをインストールする」を参照し、 まずはこのインストールを済ませてください(このインストール作業ではスマホをUSBで接続する必要もあります)。

    Google USB Driverをインストールする
    「Google USB Driver」は以下からダウンロードできます。

    https://dl.google.com/android/repository/usb_driver_r13-windows.zip

    上記のURLはAndroid Developersの以下のサイトにあるリンクの単なる抜粋です。

    https://developer.android.com/studio/run/win-usb

    ダウンロードが終わったらこれを適当なディレクトリで解凍しておきます。 例えば、E:\Android\usb_driver ディレクトリへ解凍したとしましょう。

    インストール手順については、こちらのサイトに書かれた解説がかなり詳しいかと思いますが、 ここでも簡潔な手順を以下に示します。

    1. Windows PCとスマホをUSBで繋ぎます。

    2. 繋いでおかないとデバイスマネージャに「Fire」という項目が現れません。

    3. デバイスマネージャを開きます。

    4. 「ほかのデバイス」⇒「Fire」となっている項目があるはずです。 あるいは過去に一度このドライバをインストールしたことがある場合、「ADB Interface」という名前になっているかもしれません。 これを右クリックし、開いたコンテキストメニューから「ドライバソフトウェアの更新」を選びます。

    5. 「ドライバソフトウェアの更新」ダイアログが表示されます。

    6. ダイアログ上部に「どのような方法でドライバソフトウェアを検索しますか?」と表示されます。 「コンピュータを参照してドライバソフトウェアを検索します」を選びます。

    7. ダイアログ上部に「コンピュータ上のドライバソフトウェアを参照します」と表示されます。

    8. このときダイアログ下部に「コンピュータ上のデバイスドライバの一覧から選択します」が表示されますので それをクリックします。

    9. ダイアログ上部に「次の一覧からデバイスの種類を選択してください」と表示されることがあります(されない場合もあります)。

    10. このダイアログが表示された場合はスキップし、右下にある「次へ」ボタンをを押します。

    11. ダイアログ上部に「このハードウェアのためにインストールするデバイスドライバを選択してください」と表示されます。

    12. 「ディスク使用」と書かれたボタンを押します。

    13. 「フロッピーディスクからインストール」ダイアログが表示されます。

    14. 一番右下にある「参照」ボタンを押します。

      ダイアログのタイトルには「フロッピーディスク」と書かれてはありますが、この作業でフロッピーディスクを使うことは一切ありません。

    15. 「ファイルの場所」ダイアログ表示がされます。

    16. ここで先ほど解凍したディレクトリ内へ移動します (今回の例ですとE:\Android\usb_driverです)。 トップディレクトリに android_winusb.inf ファイルが存在すると思いますので、 それを選択して「開く」ボタンを押してください。

    17. 「フロッピーディスクからインストール」ダイアログが再度、最前面に表示されます。

    18. 「製造元のファイルのコピー元」欄の表示が今選択したandroid_winusb.infが置かれたディレクトリのパスになっていることを確認します(android_winusb.infそのものではなく、それが置かれたディレクトリのパスです)。 確認しましたら「OK」ボタンを押します。

    19. ダイアログ上部に「このハードウェアのためにインストールするデバイスドライバを選択してください」と表示されます。

    20. 「モデル」とある欄に「Android ADB Interface」という項目があることを確認し、それをクリックしてください。 さらに「次へ」ボタンを押します。

    21. 「ドライバの更新警告」ダイアログが表示されます。

    22. 「はい」を押してください。

      このダイアログには「Windowsによってこのデバイスドライバがハードウェアと互換性があることを確認できなかったため このデバイスドライバをインストールしないことをお勧めします」などと表記されていると思いますが、 これは単にWindowsのDBにGoogle USB Driverの情報が登録されていないというだけの話なので 全く気にする必要はありません。

    23. さらに「Windowsセキュリティ」ダイアログが表示される場合があります(されない場合もあります)。

    24. 発行元が「Google LLC」となっていることを確認し、「インストール」を押してください。

    25. ダイアログ上部に「ドライバソフトウェアが正常に更新されました」と表示されます。

    26. その下に「Android ADB Interface」と書かれてあることを確認してください。 これでインストールは完了です。 「閉じる」ボタンを押してください。

    27. コンピュータの再起動を促すメッセージが表示される場合があります(されない場合もあります)。

    28. これが表示された場合でも今すぐに再起動する必要はありませんが、キリのよいところで再起動しましょう。

    29. 念のため、デバイスマネージャ内に、「Android Phone」⇒「Android ADB Interface」といった項目があることを確認します (Android Phoneとある項目の頭にある小さな三角ボタンを押すと展開されます)。

    30. これがあればドライバのインストールに成功しています。
    Close


    スマホに初めて接続されたPCの場合、ドライバのインストール完了後に「USBデバッグを許可しますか?」といったダイアログが(スマホ側の画面に)表示されることがあります。 その場合はそこで「OK」を押します。 ここで「OK」を押して許可設定にしておかないと以降のadbコマンドの実行に失敗してしまいます。

    それでは adb コマンドの実行に移ります。 スマホとPCがUSB接続されている状態で以下を実行してください。

    1. src/make_apk/moai/adb 内へ移動し、adb_start_server.bat を実行。

    2. 以下のように表示されれば成功です。 このメッセージはadb deamonというプログラムがPC上で起動し、スマホとの通信準備が整ったことを意味します (この段階ではまだapkファイルは転送されていません)。

      deamon started successfully
      
    3. src/make_apk/moai/adb 内へ移動し、adb_install_apk.bat を実行。

    4. 以下のように表示されれば成功です。 成功するとスマホへapkファイルが転送され、さらにインストールまで行われます。

      Performing Push Install
      Success
      


  • Linuxの場合

  • スマホとPCがUSB接続されている状態で以下を実行してください。

    adb_start_server.sh 内では sudo コマンドを伴って adb コマンド呼び出しています。 そのためお使いのLinuxで sudo コマンドが使える状態である必要があります。

    1. src/make_apk/moai/adb 内へ移動し、adb_start_server.sh を実行。

    2. 以下のように実行します。

      cd src/make_apk/moai/adb
      sh adb_start_server.sh
      

      以下のように表示されれば成功です。 このメッセージはadb deamonというプログラムがPC上で起動し、スマホとの通信準備が整ったことを意味します (この段階ではまだapkファイルは転送されていません)。

      deamon started successfully
      

    3. src/make_apk/moai/adb 内へ移動し、adb_install_apk.sh を実行。

    4. 以下のように実行します。

      cd src/make_apk/moai/adb
      sh adb_install_apk.sh
      

      以下のように表示されれば成功です。 成功するとスマホへapkファイルが転送され、さらにインストールまで行われます。

      Performing Push Install
      Success
      


adb_install_apk の実行に失敗する場合、adbサーバの起動に失敗しているか、adbサーバが自動的に終了してしまっている可能性があります。 再度 adb_start_server.bat を実行してから、もう一度お試しください。

adb_start_server が成功しているにもかかわらず、adb_install_apk の実行に失敗する場合いくつかの原因が考えられます。

例えば以下のようなエラーが表示される場合、ドライバがインストールされていないか無効な状態になっています。 あるいはスマホ側のUSB設定において「ファイル転送」モードになっていなかったり、「USBデバッグ」モードがOFFになっている可能性があります。

Performing Push Install
adb: error: failed to get feature set: no devices/emulators found

実機のLinuxからadbを実行する場合、(100%とは言えませんが)あまり問題になることはないでしょう。

一方、VirtualBox上のLinuxからadbを実行して実機に繋ぐ場合、特別な設定が必要になる場合があります。 例えばFireHDに繋ぐ場合、次の手順で行うとうまくいくかもしれません (この記事ではVirtualBox Ver6.1(Windows)上のArch Linuxと FireHD10で動作を確認しています)。

  1. VirtualBoxの「USBの設定」において「USBデバイスフィルター」に実機へのUSBフィルター(例えば「Amazon Fire[0223]」)を追加する。

  2. これは「USBデバイスフィルター」UI画面の右側、上から2番目にあるボタン(緑色の「+」マークがあるボタン)を押し、 そこで出て来るメニューからフィルター項目(例えば「Amazon Fire[0223]」)を選ぶことで行います。 (このボタンにマウスカーソルを合わせると「ホストマシンに接続された、…新規のUSBフィルターを追加します」 というよくわからない日本語のチップが出て来ると思います)。 尚、このフィルター項目はいつでも自由に削除/追加できます。 削除した状態ですと、adb devices コマンドの実行で何も表示されなくなる可能性があります。

    また、途中でVirtualBoxか実機の電源を切るなどした場合、このフィルターを一旦削除してもう一度追加しなおすなどの操作が必要かもしれません。

  3. USBを繋いだ状態で実機(例えばFireHD10)の設定でUSB接続を「ファイル転送」モードにする。

  4. 既に「ファイル転送」モードである場合は、一旦「データ転送なし」モードにし、もう一度「ファイル転送」モードを選んでください。

  5. VirtualBoxのLinuxにおいてsudo adb start-serverを実行する。

  6. これは sudoで実行した方がよいでしょう。 sudoを付けなかった場合、adb devices コマンドで no permission などと表示される可能性があります。

  7. 実機(例えばFireHD10)の画面において「USBデバッグを許可しますか?」といった旨のダイアログが出ることを確認する。

  8. これが出ればほぼ成功です。許可を選びます。

  9. VirtualBoxのLinuxにおいてadb devicesコマンドを実行し、「実機のシリアルID device」といった表示でリストされることを確認する。

  10. この状態であれば、基本的にその他のadbコマンドも正常に機能するはずです。

adb_install_apk を実行時に以下のように表示される場合、既にスマホ側にインストールされているMoaiの署名と 今新しくインストールするapkファイルの署名が一致していません。

Performing Streamed Install

adb: failed to install moai-v2.3-android.apk:
Failure [INSTALL_FAILED_UPDATE_INCOMPATIBLE: Package znkproject.moai signatures do not match previously installed version; ignoring!]

このエラーが表示されるということは、おそらく現在スマホ側にインストールされているMoaiは、Moai公式サイトからダウンロードしてインストールしたものでしょう。

その場合、あなたのPCのホームディレクトリ直下の .android/debug.keystore ファイルをMoai公式のものと一致させる必要があります。 Moaiのソース内に src/make_apk/moai/config_in/debug.keystore というファイルがありますが、これがMoai公式のものです。 これをあなたのPCのホームディレクトリ直下の .android/debug.keystore ファイルに上書きコピーしてください。 その上で再度 apk ファイルを作り直します。 これで両者の署名は一致するはずです。 作り直したapkファイルを再度インストールしてみてください。

補足: PCのホームディレクトリ
PCのホームディレクトリはOSによって異なります。

  • Windows 2000/XPの場合

  • 「C:\Documents and Settings\ログオンユーザ名」のようになります。

  • Windows Vista以降の場合

  • 「C:\Users\ログオンユーザ名」のようになります。

  • Linuxの場合

  • いわゆるホームディレクトリ「~/」です。

Close


デフォルトでは、異なるPCでビルドした apk は異なる署名がなされて生成されてしまいます。 我々Moai開発チームが作成した apk とあなたがあなたのPCでビルドした apk は、デフォルトでは異なる署名がなされてしまうということです。 この差異が生じる原因が .android/debug.keystore にあります。 デフォルトでは、このファイルはPCにより異なった内容で作成されてしまいます。

そこで、我々Moai開発チームが使っているdebug.keystoreとあなたが使うdebug.keystoreを一致させた上で apk ファイルを作成する必要があるわけです。 自ら作った apk であるのに署名で弾かれるというのはなんとも歯痒いものですが、apkがこのような仕様になっている以上仕方ありません。

apkの署名の詳細については以下を参照してください。

https://developer.android.com/studio/publish/app-signing


目次に戻る

MacOSX(Darwin)版をコンパイルする(darwin8/darwin17)

難易度:★★

MacOSX(Darwin)版について


MoaiではVer2.3よりMacOSX(Darwin)版が提供されます。 一つは darwin8(MacOSX v10.4)相当、もう一つは darwin17(MacOSX v10.13)相当になります。

darwin8 向けについては、実際にはOpenDarwin(8.0.1)上でビルド(gccを使用)とテストしたものです。 一方、darwin17 向けについては、実際にはPureDarwin(darwin17相当)上でビルド(llvm_clang Ver8.0を使用)と起動確認したものです。 どちらも使用するMakefileはMakefile_darwin.makとなります。

OpenDarwinはかなり古いもので、現在ではその後継となるPureDarwinというものもあり、 こちらの方が新しいMacOSXに対応したものになるようです。

どちらでもビルドと起動確認までは行っておりますが、 ただし現段階ではPureDarwinの安定的な実行環境を我々の仮想マシンで用意することが困難であるため、 主にOpenDarwinでテストを行っています (とはいえ、OpenDarwin上でもDarwinに関する基本的な要件のほとんどすべてをテストすることができます)。

尚、OpenDarwinが対応しているのは(さらに古のppc版を除けば)i386版のみです。 残念ながらOpenDarwin上のgccでは、x86_64版やarm64版のバイナリを作ることはできないと思われます。
iPhone版はないのか?

残念ながらMoaiはiPhoneをサポート致しませんし、 (Appleの方針が変わらない限り)これから先もサポートするつもりはございません。

技術的には MacOSX のバイナリが生成できる時点でほぼ iPhone のバイナリも生成できているようなものです。

しかしながらここからこのバイナリをiPhoneで動作する形のファイルにラップし、 さらに実際にiPhoneで実行できるように配布するためには通常XCodeが必要な上、 開発したアプリをAppleのサイトに登録することが絶対に必須となります。 登録は有料です(アプリのユーザがダウンロードする際に課金されるといったことではなく、 フリーウェアの開発者がAppleにサイトの使用料を払わなければならないということです)。 我々はこれに迎合しません。 これは技術的な問題というよりも我々とAppleとの姿勢の相違の問題です。

必要なコマンド群


イントロダクションでは、C/C++での開発において最低限必要となる基本コマンドについて述べました。

ここで今一度、Darwin(Ver8を想定)環境におけるこれらのコマンドについて確認いたしましょう。 darwin8では標準コンパイラは gcc、darwin17では標準コンパイラは llvm_clang と考えてよいでしょう。

ツールの種類 対応コマンド 対象ファイル
Cコンパイラ gcc/clang c から o へ
C++コンパイラ g++/clang++ cpp から o へ
リンカ ld(dyld/ld64) o から 実行バイナリ/dylibへ
ライブラリアン ar および ranlib o から aへ
ビルドスクリプトmake ( GNU make )Makefile_darwin.makを使用
標準ライブラリ MacOSX SDK システム標準


通常、Darwinにおいてはこれらのコマンドは既にインストールされていると思います。

コンパイルおよびインストール


  1. Darwin ターミナルを開き、インストール先のパスを環境変数 ZNK_INSTALL_DIR で指定します。

  2. export ZNK_INSTALL_DIR=$HOME/install_dir
    

    環境変数 ZNK_INSTALL_DIR で指定されたディレクトリが存在しない場合は自動的に生成されます。 Moaiの場合、$ZNK_INSTALL_DIR/moai-v$REL_VER($REL_VERはMoaiのバージョン番号で例えば2.3など)という名前のディレクトリが生成され、 必要なファイルがそのディレクトリ配下へとインストールされます。

    特にコンパイルされた実行バイナリやライブラリなどは、$ZNK_INSTALL_DIR/moai-v$REL_VER/bin/$PLATFORM という名前のディレクトリに配置されます。 ここで $PLATFORM は実行バイナリの種類を表し、例えばDarwinにおいては、darwin-i386、darwin-x86_64 といった文字列になります。 Rarakuの場合も同様で、$ZNK_INSTALL_DIR/raraku-v$RRK_VER($RRK_VERはRarakuのバージョン番号で例えば1.0など)という名前のディレクトリ配下にインストールされます。

    尚、環境変数 ZNK_INSTALL_DIR が指定されていない場合は、src ディレクトリの一つ上の階層に install_dir というディレクトリが生成され、 その配下に moai-v$REL_VER や raraku-v$RRK_VER などが配置されます。

    尚、環境変数INST_MACHINEに「i386」または「x86_64」を明示的に設定して上記を実行した場合は、それぞれ32bit版または64bit版バイナリが生成されます (ただしdarwinのバージョンによってはどちらか一方が不可能であるかもしれません)。 この環境変数を指定していない場合は、Makefile_darwin.mak 内で uname -m と uname -a コマンドの両方が実行され、 その結果により 32bit版か64bit版かが自動的に決定されます。 特にDarwinのunameでは i386 と x86_64 のいずれでも「uname -m」だけでは単に「x86」という文字列が返されます。 そのため、さらに「uname -a」コマンドの出力で「i386」か「x86_64」のどちらなのかを切り分けています。

  3. darwin17をお使いの場合のみ、Darwin ターミナルより、llvm_clangのインストール先を環境変数 ZNK_LLVM_CLANG_DIR で指定します。

  4. darwin17では /usr/bin/clang などが存在する状態のはずですので、 以下のように設定することになります。

    export ZNK_LLVM_CLANG_DIR=/usr
    

    逆にdarwin8をお使いの方は、llvm_clang はインストールされておらず gcc がインストールされた状態でしょうから、 (別途llvm_clangがインストールされていない限りは)この環境変数 ZNK_LLVM_CLANG_DIR を指定してはいけません

    Makefile_darwin.mak は環境変数 ZNK_LLVM_CLANG_DIR が定義されている場合、 llvm_clang を使用したビルドに自動的に切り替えるようになっています。 そのため、darwin17の場合、この環境変数 ZNK_LLVM_CLANG_DIR の定義は必須です。 たとえ単に「clang」と打ってllvm_clangが実行できるようになっていたとしてもです。

  5. srcディレクトリへ移動し、以下のシェルスクリプトを実行します。

  6. cd znk_project/src
    sh ./make_world.sh install
    

    これですべてのコンパイルおよびインストールが自動で行われます。

    このシェルスクリプトにより、Makefile_darwin.mak の存在する各ディレクトリへ自動的に移動しつつ make -f Makefile_darwin.mak install が実行されます。 コンパイル後の実行バイナリは各ディレクトリ内のout_dirに格納されます。
    インストール処理だけを省略したい、つまり純粋にコンパイルだけを行いたい場合は、 各々のディレクトリで直接「make -f Makefile_darwin.mak」を実行した方がよいでしょう。 「sh ./make_world.sh」と実行することもできますが、 純粋にコンパイルだけでは必要なライブラリやツールがまだ所定の位置に配置されないため、 それらに依存するツールのコンパイルにおいて、リンクに失敗するかもしれません。

以上でMoaiのコンパイルとインストールはすべて完了です。

動作確認


念のため、コンパイルおよびインストールされたmoaiが起動するか確認しておきましょう。 srcディレクトリを出て、install_dir/moai-v2.*ディレクトリへと移動し、sh moai.sh を実行します。 以下のようなメッセージが表示されればmoaiは無事起動しています。

Moai : config load OK.
Moai : target load OK.
Moai : analysis load SKIP.
Moai : Filter Loading [filters/….myf]
Moai : Plugin Loading [plugins/….dylib]
…
Moai : acceptable_host=[LOOPBACK]
Moai : blocking_mode=[0]
Moai : AutoGet PrivateIP=[192.168.…].
Moai : Listen port 8124...

Moai : ObserveR : 1-th sock events by select.


尚、最初からANYモードで起動したい場合は「-acceptable_any」オプションを付けてmoai.shを実行します (つまりコンソールより「sh moai.sh -acceptable_any」を実行します)。

例えばOpenDarwinではデフォルトではXが起動しておらず、Webブラウザもありません。 しかしそのような場合でも、ANYモードで起動した場合であれば、 別のPCのWebブラウザからネットワークを介してOpenDarwin上のMoaiを実行することはできます。
Darwinではその他のUnix系環境と異なり、環境変数LD_LIBRARY_PATHの替わりに環境変数DYLD_LIBRARY_PATHを使用します。 プログラム起動時、ライブラリがロードできない系統のエラーが発生した場合は、 環境変数DYLD_LIBRARY_PATHを適切に設定することで問題が解消する場合があります。


目次に戻る

MacOSX(Darwin)版をクロスコンパイルする(darwin17)

難易度:★★★★★

はじめに


MoaiではVer2.3よりMacOSX(Darwin)版が提供されます。 一つは darwin8(MacOSX v10.4)相当、もう一つは darwin17(MacOSX v10.13)相当になります。

ここではdarwin17版をLinux上でクロスコンパイルする方法について説明しましょう。 ただし難易度は最高レベルの★五つとなります。

もちろん MacOSX(Darwin)を直接用意できる方は、 そもそもクロスコンパイルなどせず直接その上で普通にコンパイルすればよいわけではあります。 何せ難易度が最高レベルですから、できれば避けたいと思うのが人情でしょう。

MacOSX用向けのクロスコンパイルをC言語で行いたい場合、 この記事執筆の時点では、llvm_clang(Ver3.9以降)とMacOSX SDK、そしてosxcrossと呼ばれるツールを用いるのが標準的と思われます。 (後で詳しく述べますが厳密に言えば、osxcrossに含まれる libtapi ライブラリと ld64 と呼ばれる(MacOSX用の)リンカが最低限必要となります)。 このツール自体はLinux上で動作し、従って実際にビルドを行う環境もMac(MacOSX)ではなくPC(Linux)上になります (BSD上や(古いバージョンに限れば)Cygwin上でも動作するようですが、こちらでは未確認です)。

LLVMのCコンパイラは実際のファイル名(あるいはシンボリックリンク名)としては「clang」となりますが、 Google検索などでは「C言語」を表すキーワードとして「C lang」などと記述することもあり、 つまり単なる「C言語」的な意味合いの言葉と明確に区別するため、この記事ではこれを「llvm_clang」と表記するものとします。
最近では go言語やrust言語など、様々な言語でのクロスコンパイルができるようですが、 MoaiではあくまでC言語でのクロスコンパイルを行います (というよりznk_projectのソースコードの大半がC言語で書かれている以上そうせざるを得ません)。

今回のクロスコンパイルはかなり難易度が高く、 多分AndroidのクロスコンパイルとAPK作成を合わせた手順に引けを取らない難解さです。 Moai以前に準備しなければならない開発環境の最終的なサイズも膨大で、 (我々としては極力無駄なディスクスペースを消費しない手順を厳しく吟味しましたが)最低でも1.5GB程度のディスクの空スペース、 余裕もたせるなら2GBは必要となります。

またクロスコンパイルにおいては必要な環境要件やバージョンに特に注意を払う必要があります。 初めての場合、これの配備にどこかミスを入れてしまい、しかもどこを間違えたかやなぜそれが不味いのか等を解析することも困難であるため、 トラブルなくすべてを滞りなく終わらせることは多分できないでしょう。 取り組むにはそれなりの覚悟と時間も必要です。

必要なコマンド群


イントロダクションでは、C/C++での開発において最低限必要となる基本コマンドについて述べました。

ここで今一度、Darwin(正確にはそれをターゲットとしたクロスコンパイル)環境におけるこれらのコマンドについて確認いたしましょう。

ツールの種類 対応コマンド 対象ファイル
Cコンパイラ llvm_clang c から o へ
C++コンパイラ llvm_clang++ cpp から o へ
リンカ ld64(x86_64-apple-darwin17-ld)o から 実行バイナリ/dylibへ
ライブラリアン システム標準のar または cctools-portに含まれるar(x86_64-apple-darwin17-ar)
cctools-portに含まれるranlib(x86_64-apple-darwin17-ranlib)
o から aへ
ビルドスクリプトmake ( GNU make )Makefile_darwin.makを使用
標準ライブラリ MacOSX SDK 別途ダウンロードが必要


即ち、これらのコマンドが実行できるように必要に応じてインストールすればよいということになります。

…とこう書くだけなら簡単ですが、上記のうち、リンカとライブラリアンについては、現在バイナリで直接配布はされておりません。 また実際にはこれらに加え MacOSX SDK も必要になりますが、これも中身はほぼすべてがテキストファイルです。 従って、あなたはまずこれらのツールを使ってクロスコンパイルをするという以前に、 これらのツール自体のソースコードをダウンロードし、 それを一旦llvm_clangでコンパイルして作るという地獄のフルコースから始めなければなりません!

リンカとライブラリアンがMacOSX(Darwin)用のものを用いなければならないことはまあなんとなく納得ですが、 上記でC/C++コンパイラについては通常の llvm_clang となっています。 これはどういうことでしょうか?

そもそも llvm_clang は最初からクロスコンパイルの機能をある程度は持っており、 クロスコンパイル用の複雑なオプションを自分で適切に指定しなければならないものの、 別に特別なツールを使わなくても直接 llvm_clang を実行することでMacOSX用のオブジェクトファイル(Mach-O形式ファイル)までは作成できます。

一方、そこから先、つまりリンクについては、使用するのはllvm に付属するリンカではないことに注意してください。

llvm に標準で付属するリンカは lld と呼ばれますが、今回はこれを使用しないということです。

リンクについては、ld64と呼ばれるリンカを使うことになります。 ただしこれの実際のファイル名は、x86_64-apple-darwin17-ld などとなることもあります。

「x86_64-apple-darwin17」というやけに長ったらしいプレフィックスがついていますが、 これは別にツールの作者の趣味で付けているわけではなく、 クロスコンパイル界隈で一般にtarget triple(target triplet)と呼ばれている記法になります (とはいえ、現実には「記法」というほど論理的な統一が取れているわけでもなく、 かえって混乱をもたらしているだけのような気もします)。

ld64 は本来MacOSX用のリンカですが、これから作るのはこれを Linux上でも動作するようにビルドしたものです。 最低限必要なものという観点からすれば、 今回のミッションではこの ld64 のビルドがいわばラスボスです。 またこの ld64 のビルドにあたってはいくつかの下請けライブラリが必要となりますが、 これらも osxcrossのサイト(というより正確にはosxcrossの作者であるThomas Pochtrager氏のサイトと言うべきですが)で そのソースコード等が配布されています。 特に、apple-libtapi と呼ばれるライブラリはこれ自体が10個程度のライブラリから構成される恐るべき巨大さです。 今回のミッションではこの apple-libtapi のビルドがいわば前半の中ボスにして最難関となるでしょう。

この記事ではこれらの攻略法(といっても我々が用意したGNU Makefileを順番に実行するだけですが)について詳しく述べます。
因みに、osxcross では x86_64-apple-darwin17-wrapper といった名前のツールも提供されますが とりあえずこの記事ではこれは不要です。

これはクロスコンパイラ用のオプションを自動的に付加しつつ、上記のコンパイラやリンカを中で自動的に呼び出すといったもので、 言ってしまえば「たらい回し」のように仕事を下請けに流すというまるで大企業の連中のような役回りですが、このようなツールをラッパーと呼びます。 「あれば便利だね」くらいのツールですが、トラブルが起きた際、実際に誰がどのように仕事をしているのかわからなくなるといったデメリットもあります。

これについては、最後のテストの項目で改めて考察することに致しましょう。
最終的に MacOSX SDK に含まれるファイルともリンクさせる必要がありますが、 既に述べた通り、MacOSX SDK は通常のインポートライブラリとは異なり、tbdと呼ばれる特殊なファイルから構成されます。 Linux標準で提供されるいわゆるgcc付属のリンカ(/usr/bin/ld)やllvm が提供するリンカ(lld)では、 この tbd を取り扱うことができません。 一方、ld64 はこれを取り扱うことができ(実はこの tbd を取り扱うためのライブラリがapple-libtapiライブラリに他ならないわけですが)、 そのためこちらが必要になるわけです。

ライブラリアンについても説明しておきましょう。 ar についてはシステム標準の ar と cctools-port においてビルドする x86_64-apple-darwin17-ar のどちらを使っても構いません。 ranlib については cctools-port においてビルドする x86_64-apple-darwin17-ranlib が必須となります。 注意点として、システム標準の ar を使って静的ライブラリを作った場合、直後に x86_64-apple-darwin17-ranlib を実行しておかなければなりません。 一方、x86_64-apple-darwin17-ar を使って静的ライブラリを作った場合、直後に x86_64-apple-darwin17-ranlib を実行する必要はありません

というより、x86_64-apple-darwin17-ar を使った場合、 内部で自動的に、x86_64-apple-darwin17-ranlib も呼び出され、 既にこれが実行済みとなります。

Moai自体をクロスコンパイルするためのMakefileは(通常のコンパイルと同様に)Makefile_darwin.mak を使います。 これは gmake(GNU make) 用のMakefile です。 しかしこれを使うのは ld64 のビルドが無事完成してからです。 そこに辿り着くまでが大変な道のりになります。

因みに ld64 のビルドで使う llvm_clang のバージョンについては(主にC++の互換性が原因で)かなり気をつけなければなりませんが、 Moaiのビルドに限っては(基本的にC言語しか使っておりませんので)、どのバージョンのllvm_clangでも大丈夫だとは思います。
オリジナルの osxcross のビルド方法に忠実に従う場合、apple-libtapi のビルドにおいて「cmake」とやらが必要ということになっておりますが、 この記事で述べる方法でapple-libtapiをビルドする場合、cmakeは一切不要です。 というよりあれ(cmake)は実質何もやっておらず、今回に限らず普通に考えても不要です。 GNU make さえあれば何も問題ありません。

ここまでをまとめましょう。 我々はまず、llvm_clang、osxcrossにおいて提供されるリンカ(ld64および関連ライブラリ)、 MacOSX SDK(ヘッダファイルとtbdファイル) のざっくり言って三つを用意しなければなりません。 しかもこれらの要求するバージョンの組み合わせを、適切かつ完全に満たす必要があります。

osxcrossの概要とディレクトリの準備


ここで改めてosxcrossとはどういったものなのかについて改めて紹介しましょう。 この記事では既にいくつかリンクを貼っていますが、https://github.com/tpoechtrager/osxcrossが公式サイトとなります。

オーストリア(ウィーン大学)にThomas Pochtrager(トーマス・ペヒトラガー)という名前の言語学者がいるようですが、 osxcrossの作者はおそらくその人本人でしょう。 尚、この方は多分 Apple とは無関係な方だと思います。 よって osxcross は言ってしまえば公式ではなくサードパーティ製のツールということになります。

本来こういったものは公式である Apple がもっと手厚くサポートしてやればいいのではないかとは思うのですが、 GoogleのAndroidに対するサポートと比べ、AppleのDarwinに対する(特にオープンソースに関する)サポートは消極的と言わざるを得ません。 例えばAndroid SDKは、特にGoogleに登録の必要もなく公式からダウンロードできますが、 一方でMacOSX SDKは、(公式からダウンロードする場合)わざわざ登録が必要な上、SDK単独での入手ができません。 そのため、クロスコンパイル一つ行う材料を揃えるにしても、公式サイトを経由した入手は色々と不便であり、 サードパーティを利用した方が手っ取り早いことが多いです。 かつては存在したOpenDarwinの公式サイトが今では消滅してしまっているという例もあります (現在ではPureDarwinというプロジェクトがその後継となっているようです)。

osxcross は Linux上で動作するツールです。 よく調べてみるとその他の環境にも対応している節はありますが、 Linux を用意した方が確実でしょう(この記事ではArch Linuxを使いますが、Linuxであれば何でもよいです)。

まずは osxcross 関係のファイル(xar、apple-libtapi、cctools-portの三つになりますが)をダウンロードし、 これらすべての親となるディレクトリを予め手動で作っておきます。 どこでも構いませんが、ここでは例えばホームディレクトリ$HOME直下のDownloads/DarwinXC というディレクトリとしましょう。

「XC」に大した意味はありませんが、この記事では「クロスコンパイル」の略称として使います。

この先、osxcross関係、MacOSX SDK、llvmなどのディレクトリをここに作成することにします。

また$HOME/Downloads/DarwinXC/install_dir というディレクトリを用意し、 そこにこれから作成するツールやライブラリ群のすべてをインストールするものとしましょう。

このディレクトリ DarwinXC 全体としては、最終的にはおよそ2GB程度(llvm関係が800MB、MacOSX SDKが650MB、osxcross関係が550MB程度)を占めることになるかと思います。 ディスクに余裕がない方は予めエロ動画不要なファイルを消すなどして確保しておきましょう。

コンパイルする前の時点では、トータルのサイズはそこまでたいしたことはありませんが、 コンパイルが進み、オブジェクトファイルが生成されると、サイズがどんどん膨れ上がります。 特にMacOSX SDKは圧縮された状態(ダウンロード時の状態)では50MB程度ですが、 展開すると600MBにもなります(結構な圧縮率ですが、多分中身がほとんどテキストファイルから構成されるためでしょう)。

osxcrossがサポートしているアーキテクチャとバージョン


どのバージョンのDarwin(MacOSX)で、どんなアーキテクチャがサポートされているかをおおよそ把握しておきます。 以下は osxcross Ver1.1 の build.sh の冒頭にある記述です。

case $SDK_VERSION in
  10.4*)  TARGET=darwin8;  X86_64H_SUPPORTED=0; I386_SUPPORTED=1; NEED_TAPI_SUPPORT=0; OSX_VERSION_MIN_INT=10.4;  ;;
  10.5*)  TARGET=darwin9;  X86_64H_SUPPORTED=0; I386_SUPPORTED=1; NEED_TAPI_SUPPORT=0; OSX_VERSION_MIN_INT=10.5;  ;;
  10.6*)  TARGET=darwin10; X86_64H_SUPPORTED=0; I386_SUPPORTED=1; NEED_TAPI_SUPPORT=0; OSX_VERSION_MIN_INT=10.5;  ;;
  10.7*)  TARGET=darwin11; X86_64H_SUPPORTED=0; I386_SUPPORTED=1; NEED_TAPI_SUPPORT=0; OSX_VERSION_MIN_INT=10.5;  ;;
  10.8*)  TARGET=darwin12; X86_64H_SUPPORTED=1; I386_SUPPORTED=1; NEED_TAPI_SUPPORT=0; OSX_VERSION_MIN_INT=10.5;  ;;
  10.9*)  TARGET=darwin13; X86_64H_SUPPORTED=1; I386_SUPPORTED=1; NEED_TAPI_SUPPORT=0; OSX_VERSION_MIN_INT=10.5;  ;;
  10.10*) TARGET=darwin14; X86_64H_SUPPORTED=1; I386_SUPPORTED=1; NEED_TAPI_SUPPORT=0; OSX_VERSION_MIN_INT=10.5;  ;;
  10.11*) TARGET=darwin15; X86_64H_SUPPORTED=1; I386_SUPPORTED=1; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.5;  ;;
  10.12*) TARGET=darwin16; X86_64H_SUPPORTED=1; I386_SUPPORTED=1; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.5;  ;;
  10.13*) TARGET=darwin17; X86_64H_SUPPORTED=1; I386_SUPPORTED=1; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.5;  ;;
  10.14*) TARGET=darwin18; X86_64H_SUPPORTED=1; I386_SUPPORTED=0; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9;  ;;
  10.15*) TARGET=darwin19; X86_64H_SUPPORTED=1; I386_SUPPORTED=0; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9;  ;;
*) echo "Unsupported SDK"; exit 1 ;;
esac

この記述はosxcrossバージョン1.1(で配布されるライブラリとツール群)では、 上記の範囲のMacOSX(Darwin)をサポートしていることを示しています。

一列目にあるのが、いわゆるMacOSXのバージョンと考えてよいでしょう(正確にはMacOS SDKのバージョンです)。

二列目にあるTARGETで指定されているのがDarwinのバージョンです。 これによれば、例えばdarwin8(Darwin Ver8)はMacOSX 10.4相当、darwin9(Darwin Ver9)はMacOSX 10.5相当であることがわかります (換言すればMacOSX 10.4に含まれるDarwinのバージョンは8、MacOSX 10.5に含まれるDarwinのバージョンは9ということです)。

三列目にあるX86_64H_SUPPORTEDの値が1の場合は、そのDarwinでx86_64hなるものがサポートされていることを意味し、 値が0の場合はサポートされていないことを意味します。 これによれば、darwin11(MacOSX 10.7相当)まではx86_64hなるものがサポートされておらず、 darwin12(MacOSX 10.8相当)以降はこれがサポートされていることがわかります。 とはいえ、そもそもこのx86_64hなるものが何なのか(通常のx86_64と同じものなのか違うものなのか)がよくわかりませんね。 これについてはまた後で考えましょう。

四列目にあるI386_SUPPORTEDの値が1の場合は、そのDarwinでi386版がサポートされていることを意味し、 値が0の場合はサポートされていないことを意味します。 これによれば、darwin17(MacOSX 10.13相当)まではi386版がサポートされていますが、darwin18(MacOSX 10.14相当)以降はなんとi386版のサポートが 打ち切られていることがわかります。 i386版をビルドする場合はdarwin17(MacOSX 10.13相当)以前をターゲットとしなければなりません

znk_projectではWindows/Linuxにおいてはi386(x86 CPU 32bit)版を示す識別子としてx86、x86_64(x86 CPU 64bit版)版を示す識別子としてx64を使用しています (その方が短く済み、かつ文字数が同じに揃うためです。またWindows界隈ではその識別子が主に使われているためです)。 一方、Darwin版では識別子としてi386、x86_64を使用します。 これはそちらの方がクロスコンパイル時に指定するオプションと相性がよいためです。

五列目にあるNEED_TAPI_SUPPORTの値が1の場合は、そのDarwinでTAPIがサポートされていることを意味し、 値が0の場合はサポートされていないことを意味します。 TAPIとは、MacOSX SDKにおいて tbd ファイルによるリンクを行う機能(API)のことです。 今回ビルドするリンカ ld64 ではまさにこの tbd によるリンクが肝となります。 従って基本的にはこれがサポートされている darwin15 (MacOSX 10.11相当)以降をターゲットとする方が確実でしょう。

六列目にあるOSX_VERSION_MIN_INT はおそらく動作するMacOSXのバージョンの最小値を意味するのでしょう。 これについては後でもまた考えます。

念のため、osxcross Ver1.5 の build.sh も見ておきましょう。 以下はその冒頭にある記述です。

case $SDK_VERSION in
  10.6*)   TARGET=darwin10;   SUPPORTED_ARCHS="i386 x86_64"; NEED_TAPI_SUPPORT=0; OSX_VERSION_MIN_INT=10.6 ;;
  10.7*)   TARGET=darwin11;   SUPPORTED_ARCHS="i386 x86_64"; NEED_TAPI_SUPPORT=0; OSX_VERSION_MIN_INT=10.6 ;;
  10.8*)   TARGET=darwin12;   SUPPORTED_ARCHS="i386 x86_64 x86_64h"; NEED_TAPI_SUPPORT=0; OSX_VERSION_MIN_INT=10.6 ;;
  10.9*)   TARGET=darwin13;   SUPPORTED_ARCHS="i386 x86_64 x86_64h"; NEED_TAPI_SUPPORT=0; OSX_VERSION_MIN_INT=10.6 ;;
  10.10*)  TARGET=darwin14;   SUPPORTED_ARCHS="i386 x86_64 x86_64h"; NEED_TAPI_SUPPORT=0; OSX_VERSION_MIN_INT=10.6 ;;
  10.11*)  TARGET=darwin15;   SUPPORTED_ARCHS="i386 x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.6 ;;
  10.12*)  TARGET=darwin16;   SUPPORTED_ARCHS="i386 x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.6 ;;
  10.13*)  TARGET=darwin17;   SUPPORTED_ARCHS="i386 x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.6 ;;
  10.14*)  TARGET=darwin18;   SUPPORTED_ARCHS="x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9 ;;
  10.15*)  TARGET=darwin19;   SUPPORTED_ARCHS="x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9 ;;
  10.16*)  TARGET=darwin20;   SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9 ;;
  11|11.0*) TARGET=darwin20.1; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9 ;;
  11.1*)   TARGET=darwin20.2; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9 ;;
  11.2*)   TARGET=darwin20.3; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9 ;;
  11.3*)   TARGET=darwin20.4; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9 ;;
  12|12.0*) TARGET=darwin21.1; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9 ;;
  12.1*)   TARGET=darwin21.2; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9 ;;
  12.2*)   TARGET=darwin21.3; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9 ;;
  12.3*)   TARGET=darwin21.4; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9 ;;
  12.4*)   TARGET=darwin21.5; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9 ;;
  13|13.0*) TARGET=darwin22.1; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9 ;;
  13.1*)   TARGET=darwin22.2; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9 ;;
  13.2*)   TARGET=darwin22.3; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9 ;;
  13.3*)   TARGET=darwin22.4; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.9 ;;
  14|14.0*) TARGET=darwin23;   SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.13 ;;
  14.1*)   TARGET=darwin23.1; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.13 ;;
  14.2*)   TARGET=darwin23.2; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.13 ;;
  14.3*)   TARGET=darwin23.3; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.13 ;;
  14.4*)   TARGET=darwin23.4; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.13 ;;
  14.5*)   TARGET=darwin23.5; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.13 ;;
  14.6*)   TARGET=darwin23.6; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.13 ;;
  15|15.0*) TARGET=darwin24;   SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.13 ;;
  15.1*)   TARGET=darwin24.1; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.13 ;;
  15.2*)   TARGET=darwin24.2; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.13 ;;
  15.3*)   TARGET=darwin24.3; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.13 ;;
  15.4*)   TARGET=darwin24.4; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.13 ;;
  15.5*)   TARGET=darwin24.5; SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.13 ;;
  26|26.0*) TARGET=darwin25;   SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.13 ;;
  26.1*) TARGET=darwin25.1;   SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.13 ;;
  26.2*) TARGET=darwin25.2;   SUPPORTED_ARCHS="arm64 arm64e x86_64 x86_64h"; NEED_TAPI_SUPPORT=1; OSX_VERSION_MIN_INT=10.13 ;;
  *) echo "Unsupported SDK"; exit 1 ;;
esac

この記述はosxcrossバージョン1.5(で配布されるライブラリとツール群)では、 上記の範囲のMacOSX(Darwin)をサポートしていることを示しています。

今回はI386_SUPPORTEDやX86_64H_SUPPORTEDという変数が消え、 替わりにSUPPORTED_ARCHSという変数が使われていますが、それ以外はバージョン1.1と表の見方は同じです。

SUPPORTED_ARCHSの値はサポートしているCPU(MACHINE)のアーキテクチャを意味するのでしょう。 ここに"i386 x86_64 x86_64h"といった記述が見られますので、 どうやら x86_64h とは通常の x64(x86_64)とは別の64bit CPUアーキテクチャであることがわかります。 また、(x86_64hではなく)x86_64については、darwin11(MacOSX 10.7相当)以前でもサポートされているようですが、 darwin12(MacOSX 10.8相当)以降では x86_64 と x86_64h の両方がサポートされています。 それゆえ、darwin12以降の方がx86系64bit CPUのサポートは手厚いであろうと推察できます

OSX_VERSION_MIN_INT はおそらく動作するMacOSXのバージョンの最小値を意味するものと思われますが、 以下では darwin17、darwin18、darwin20 を例にこのあたりの値の変遷を見ます。

  • darwin17(MacOSX 10.13相当)
  • SDKのバージョンは 10.13 ですので、これは基本的にはMacOSX 10.13 上で動作するバイナリを作ることを目的としたものです。 ただし「OSX_VERSION_MIN_INT=10.6」という記述がありますので、 MacOSX 10.6 以降であれば、MacOSX 10.13よりも古いMacOSX上でも動作可能なバイナリが生成されると考えられます。

    よく見るとosxcross バージョン1.1におけるbuild.sh では、 darwin17(MacOSX 10.13相当)の行で「OSX_VERSION_MIN_INT=10.5」と記述されており、 osxcross バージョン1.5におけるbuild.sh の記述と食い違っています。 この記事では両者の積集合をとって「OSX_VERSION_MIN_INT=10.6」と解釈することに致します。

  • darwin18(MacOSX 10.14相当)
  • 「OSX_VERSION_MIN_INT=10.9」となっており、darwin17と比べて値がいっきに増えています。 そのため、より広範囲の(古いバージョンの)MacOSXにまで対応したい場合、 darwin18 は darwin17より不利ということになります (さらに言えば、darwin18(MacOSX 10.14相当)以降はi386(x86版)のサポートが打ち切られています)。 darwin12、darwin17、darwin18の三つから(なるべく古いMacOSXにも対応するように)なるべく新しいSDKを選ぶと、 おそらくx64版を作る場合はdarwin17(MacOSX 10.13)を選ぶのが最適であろうと判断できます (さらに言えば、現在入手可能なPureDarwinもdarwin17相当です)。

  • darwin20(MacOSX 10.16相当)
  • arm64のサポートが追加されています (さらに言えば、これ以降、SUPPORTED_ARCHSの値は一定です)。 しかもOSX_VERSION_MIN_INTの値はdarwin18と同じく10.9のまま変わりません。 arm64のサポートを重視するならこれを選択するのがよいのでしょう。

ここまでで判明した情報をまとめましょう。 まだ暫定案で変更する可能性はありますがとりあえず次の方針で考えます。
  • x86/x64版をビルドする場合 : darwin17(MacOSX 10.13相当) を選ぶものとする
  • arm64版をビルドする場合 : darwin20(MacOSX 10.16相当) を選ぶものとする

MacOSX SDKの入手


次にMacOSX SDKを入手します。

ところでSDKとは何でしょうか?

MacOSXの機能の実体は当然ながらMacOSX本体に備わっています。 ここで言う「機能」とはC言語で言うところの「関数」に読み変えることもできます。 関数の実体そのものは通常MacOSX本体に備わっているわけです(この関数はアプリケーション実行時に動的にロードされます)。 まあ当然といえば当然な話です。

しかしこの関数をプログラマが使うためにはそのインターフェースを記述したC言語のヘッダファイルが必要になります。 あるいはその関数名だけが定義され、プログラマが作るアプリケーションとMacOSX本体との橋渡しになる小さなライブラリ (VCでの言葉を借りればインポートライブラリに相当するもの)が必要です。 SDKとはこのようなヘッダやインポートライブラリの集合体(本体の機能と接続するためのある種のアダプタ)と考えてもらって差し支えありません。 よってCコンパイラとは別にこのSDKも必要になるというわけです。

特にMacOSX SDKの場合、このインポートライブラリに相当するものとして、 tbd ファイルと呼ばれるものを使用しています。 ただしVCのインポートライブラリはバイナリファイルですが、tbd ファイルはテキストファイルとなっています (従ってエディタで中身を確認するのが容易く、その気になればテキストエディタで自分で書くこともできるのでしょう)。 また、SDK内には極わずかですがオブジェクトファイル(Mach-O形式)が数個含まれており、 これらについてはバイナリファイルとなります。

MacOSX SDKは https://github.com/phracker/MacOSX-SDKs というサイトから入手するのがクロスコンパイル界隈では昔からの定番のようです。 ただし現在では、もう一つの対抗馬として https://github.com/joseluisq/macosx-sdks というサイトもあります。

phracker の方は、通称「phracker」という名前のデジタルアーティストが作ったサイトのようです。 一方、joseluisq の方は、ペルーのプログラマ Jose Quintana という方が作ったサイトのようです。

どちらを使うべきでしょうか? 一般的にはphrackerの配布するSDKの方が歴史が長く実績があるようですが、 ここは少し両者に実際に含まれるファイルを厳密に比較検証することに致しましょう。

我々が両者(サンプルとしてSDK Ver10.11を調査)をdiffで検証した結果、 どちらも含まれているファイル自体は(本質でない冗長性を除き)基本的に同一であることを確認しました。 ただしjoseluisqが配布しているSDKの方が若干ファイルサイズが大きくなっています。 その理由は、phrackerが配布しているSDKの方には一部C++関係のヘッダの抜け(具体的には/usr/include/c++/v1ディレクトリの欠損)や、 シンボリックリンクに関する誤りがわずかに存在している一方で、 joseluisqが配布しているSDKではそれが補われたり修正されている(シンボリックリンクを使わず同じファイルを冗長に生成している)ためです。 そのため、特に /usr/include/c++/v1 ディレクトリの存在に拘るならば、 joseluisqのSDKの方がより万全と言えますが、ビルドの途中で問題が生じなければどちらでもよいでしょう。 この記事ではjoseluisqのSDKの方を使うことに致します。

Appleの公式サイトからも入手できるようですが、この記事では使いません。 ダウンロードにはまず登録が必要な上、入手できるSDKのバージョンも新しいものに限られる(古いSDKは入手不可能である)からです。 これだけでも不採用の理由としては十分ですが、 さらに言えば、SDKだけをピンポイントにダウンロードできるわけではなく、 一旦「Command Line Tools for Xcode」を丸ごとダウンロードし、 そこからSDKだけを抜き出すといった手間が生じます。 しかも「Command Line Tools for Xcode」のダウンロードファイルサイズはバージョンにもよりますがおよそ400MBから700MBにもなります (そのほとんどは今回のミッションに不要なものです)。 ダウンロード時の状態(つまり圧縮された状態)でさえこのサイズですので、 展開するとさらにとんでもないサイズに膨れ上がることが予想できます。

次にどのバージョンのSDKをダウンロードするかですが、 現時点では、我々はSDK Ver10.13またはVer10.16の採用を暫定案としております。 SDK Ver10.13 については以下からダウンロードすればよいでしょう (この記事ではjoseluisq版を使いますが、一応phracker版のリンクも示しておきます)。


ところがSDK Ver10.16 についてはphrackerとjoseluisqの双方においてリンクが存在しません。 どうやらSDK Ver10.16 は配布されていないようです。 そのためここは方針を転換し、darwin20(MacOSX 10.16相当)ではなく その次のバージョンを検討することにしましょう。

次のバージョンはdarwin20.1(MacOSX 11.0相当)になります。 ところがSDK Ver11.0 についてはphrackerでは配布されていますが、joseluisqでは配布されていないようです。 そのためさらに次のバージョンを考えます。

さらに次のバージョンはdarwin20.2(MacOSX 11.1相当)になります。 SDK Ver11.1 であればphrackerとjoseluisqの双方において配布されています。 よってarm64までサポートするならdarwin20.2(MacOSX 11.1相当)を用いるのがよさそうです。 これを最終案としましょう。 SDK Ver11.1 のダウンロード先も以下に載せておきます。


念のために補足しますが、MacOSX10.13.sdk.tar.xz と MacOSX11.1.sdk.tar.xz の両方が必要というわけではなく、どちらか一方で結構です。 arm64までサポートしたいなら MacOSX11.1.sdk.tar.xz を使うことになりますし、 i386(x86)までサポートしたいなら、MacOSX10.13.sdk.tar.xz を使うことになります。 x64版だけが必要な場合、どちらでも構いません。

llvm_clangの入手


まずはどのバージョンの llvm_clang を使うかを選定することが非常に重要です。 CHANGELOGを注意深く確認しましょう。

オープンソースのコードをビルドするとき、ほとんどの場合CHANGELOGなど読まないと思いますが(かく言う我々もそうですが)、 今回についてはこの情報は最重要事項になります。 これを怠ると結局後でドツボに嵌る可能性が高いです。

とはいえ全部読むのは面倒ですので、ここでは v0.15 に注目します(結局読まんのかい!)。 以下のように記述されています。

/****************************** v0.15 ********************************/

Changed:
 * Updated cctools to 895
 * Updated ld64 to 274.2 (without TAPIv2 support)

Added:
 * Support for ThinLTO:
   -> https://clang.llvm.org/docs/ThinLTO.html
   -> http://blog.llvm.org/2016/06/thinlto-scalable-and-incremental-lto.html
   -> Requires LLVM/Clang >= 3.8. LLVM/Clang >= 3.9 is recommended.
 * USE_CLANG_AS build.sh option:
   -> Use Clang as assembler.

「Requires LLVM/Clang >= 3.8. LLVM/Clang >= 3.9 is recommended.」とある部分が重要です。 この情報からllvm_clangのバージョンは少なくとも3.8以上が必要であることがわかります。

これより古いllvm_clangを敢えて使うなら、コンパイルエラーの連発を覚悟しなければなりません。

敢えて使う
実際にosxcross Ver1.5において、llvm_clang Ver3.7を敢えて使ってみましたが、 (おそらくllvm_clangの不備ないしはC++のバージョンが古いことが関係していると思いますが) あるC++クラスにおいて複数のコンストラクタ(引数の型や個数だけが異なる)があり、 そのオーバーロード解決ができないなどといったエラーが連発し、 結局どのコンストラクタが「当たり」なのか我々人間様が見てもよくわからない言わばコンストラクタのモグラ叩きとでも形容しましょうか? C++のオーバーロードの一番邪悪な部分が露見しているとでも言いましょうか? 手に負えたものではありません。 尚、Ver3.9ではこの不備が修正されているのでしょう。 一転して清々しいほどにこのエラーが発生しません。

Close


特にC++関係のエラーは厄介で、バージョンの食い違いによりいとも簡単にコンパイルエラー等が出て失敗するでしょう。 そのコンパイルエラーはちょっとやそっとのことで直せるものもありますが、 ほとんどの場合、ちょっとやそっとのことで直せる類いのものではありません。 たとえあなたがC++プログラマとして自信満々であったとしても、今回の場合、これと格闘するのはやめておいた方が無難でしょう。

C++と格闘する
クマと素手で格闘するようなものですが、 それでもやるのですね?

やる
例えば osxcross-v1.5系列の apple-libtapi と cctools-portのビルドにおいては、 次のようなシナリオが想定されます。

あるモジュールAでは古いC++のバージョンの範囲でコンパイルできます。 この調子で次のモジュールBもコンパイルを試みましたが、 モジュールBでは文字列型として(std::stringではなく)std::string_view というクラスが随所に使われており、 またこのクラスはC++17で導入されたものであるため、モジュールAと同じコンパイルオプションでコンパイルしてしまうとコンパイルエラーとなります。 かといってこれらをstd::stringに修正するのは使用箇所が多すぎるため現実的ではなく、 結局このモジュールBではC++17としてコンパイルすることにしました。

さてここで何が起きるか予想できるでしょうか? apple-libtapi や cctools-port に含まれるソースファイルの数は膨大なため、ビルドには著しく時間がかかります。 そのためできることなら最初からビルドのし直しは避けたいものです。 モジュールAは既にコンパイル済みですからいちいちcleanしたくはないと普通は思うでしょう。

またエラーになった関係で、コンパイルフラグをモジュール毎に多少変えざるをえない状況であるかもしれません。 要するにC++17としてコンパイルしたオブジェクトファイルと、古いC++としてコンパイルしたオブジェクトファイルを混ぜてリンクするような状況が容易に発生し得るということです。

しかし混ぜてリンクすることが何だと言うのでしょうか? C++のバージョンが違うとはいえ、最終的に同じllvm_clangコンパイラで生成した同じオブジェクトファイルです。 一旦オブジェクトファイルにしてしまえばバージョンの違いも何もないように思えます。 混ぜてリンクしたところで同種のオブジェクトファイルがリンクできるのは当然のことであり何の問題もないように思われますが、 これがなんとリンクエラーとなる可能性があります! なぜでしょうか?

モジュールAとBでは確かに同じフォーマットのオブジェクトファイルが生成されますが、 C言語とは異なり、C++ではご存知のようにコンパイルした時にマングリングと呼ばれるシンボル名の修飾が行われます。 問題はこのマングリングのされ方がC++のバージョンによっても違うことがあるということです。 それがたとえ全く同一のC++コンパイラであり、 ソースコード上も全く同じ関数名、引数型、引数の個数、戻り値型であってもです! そこに埋め込まれる関数のシンボル名がモジュールAとBで異なるといった状況が発生します。

全く同一のC++コンパイラ、全く同じ関数名、引数型、引数の個数、戻り値型であるならば、 (そもそもオーバーロードにおいて、これらの要素を基にシンボルを区別することがマングリングの目的であることを考えれば) マングリングされた結果が同じになるのは当然とおそらくC++に慣れた方ほど思うことでしょう。 その意味でC++に慣れた方ほど嵌りやすい罠とも言えます。
正確に言えば、これはC++そのものというよりむしろコンパイラの方の都合です。 まあ考えてみれば確かにC++のマングリングの方式に「標準」などないわけですから、 このようなことが起きても合法というか、ある意味仕方ないのかもしれません。 gccの場合、Ver5.1以降でC++のABI(マングリングの仕方に影響)が変更されました。 llvm_clangも基本的にgccと互換のコンパイラですので、gccと似た問題が存在する可能性があります。

このマングリングされたシンボル名は人間が読むには非常に辛い文字列であることに加え、 場合によってはマングリングのされ方が一致することもあるのがまた問題を気づきにくくするかもしれません。 引数や戻り値の型としてPOD、つまりクラスではないプリミティブな型だけが使われている場合、 おそらくどのバージョンのC++であれ、マングリングされたシンボル名は一致するでしょう。 一方、引数や戻り値の型としてNon-POD、つまりクラスが使われている場合、 マングリングされたシンボル名はC++のバージョンによって異なる可能性があります。

C++のちょっとしたバージョンの違いですら、これだけのことが起こってしまいます。 前途多難ですが、残念ながらこれ以外にも懸念される事項は山程あるのです。

Close


Close
また v0.15 以降のバージョンの記述をざっくり見てもらえばわかりますが、 これ以降、v1.5(最新バージョン)まで llvm_clang のバージョンについて注意している記述は見当たりません。 よって、基本的にllvm_clangのバージョンがVer3.9以降ならば最新バージョンに至るまで大丈夫であろうと予想できます。

では実際にllvm_clang Ver3.9のインストールに入ります。

ただしその前にお使いのLinuxで既にこれがインストールされているかを確認してください。 ターミナルから次のように実行します。

clang --version

おそらく「Command not found.」と表示される確率が一番高いとは思いますが、 この場合は llvm_clang がインストールされていませんので、これをダウンロードする必要があります。

運よく「clang version 3.9.0」と出ればもうllvm_clangはお使いのシステムにインストールされています。 何もする必要はないです。

「clang version」といった表示が出たとしても、次に表示される数字が「3.8.0」より小さい場合(例えば「3.7.0」など)、 バージョンが古いですので、この場合は別途 llvm_clang Ver3.9 をインストールする必要があります。 (Ver3.8でもよいですが、osxcrossのCHANGELOGではVer3.9の方をrecommended(推奨)と書かれておりますので、この記事でもVer3.9を使うことにします)。 次に表示される数字が「3.9.0」以上の場合であれば、基本的には多分問題ないでしょう (ただし我々が実際に確認したのはVer3.9のみです)。

llvm_clangのインストールはお使いのLinuxのパッケージ管理システムから行うこともできますが、 システムがllvmで汚染されるのを嫌うならば、 まずはllvmの公式サイト から直接バイナリを$HOME/Downloadsディレクトリ等へダウンロードするとよいでしょう。

「Download LLVM 3.9.0」と書かれた表の「Pre-Built Binaries:」と書かれた箇所に並んでいるリンクがバイナリとなります。 Arch Linuxの場合「Clang for x86_64 Ubuntu 16.04」を選ぶとよいでしょう。 以下にリンクも示しておきます。


「Ubuntu」と表記されておりますが、同じLinuxではあるのでバイナリのフォーマットは他のディストリビューションと同じです。 問題になるとすれば依存ライブラリの違いだけですが、gccまでインストールされている環境であれば、 llvmが依存するライブラリの種類もこれと大差ありません。 おそらくVer3.9を使う限りは問題はないでしょう。

Alpine Linuxなどちょっと風流なものを使っている場合は、Ubuntuのバイナリではダメかもしれません。 おそらく glibc 関係のエラーでこけます。 これはAlpine LinuxではglibcではなくmuslがC言語標準ライブラリだからです。
「Clang for AArch64 Linux」というリンクもありますが、この「AArch64 Linux」は arm64版Linux を意味しています。 「Arch Linux 64bit版 Linux」などといった意味ではないので、Arch Linuxをお使いの方はお間違えなく。

ダウンロードが終わりましたら、ターミナルから以下のように実行しましょう($HOME/Downloadsディレクトリ直下にダウンロードしたものとします)。

cd $HOME/Downloads
tar xvf clang+llvm-3.9.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz
mv clang+llvm-3.9.0-x86_64-linux-gnu-ubuntu-16.04 llvm_clang_install_dir

これで完了です。 $HOME/Downloads/llvm_clang_install_dir配下にllvm_clangがインストールされました!

clang+llvm-3.9.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz の方は削除して構いません。 圧縮ファイルとはいえ、164MB もありますので放置しておくには割とバカにならないサイズです。

以下を実行し、clangコマンドが無事に実行されるかを確認してください。

cd $HOME/Downloads/llvm_clang_install_dir
cd bin
./clang

「clang-3.9: error: not input file」などといったエラーが出れば成功です。 しかしながらもしも以下のようなエラーが出た場合、対処が必要です。

./clang: error while loading shared libraries: libtinfo.so.5: Cannot open shared object file: No such file or directory

このようなエラーが出た場合、まず「libtinfo.so.5」に一番近い名前のファイルが /usr/lib 等にないかを「ls -al」コマンドなどで調べます。 筆者の環境(Arch Linux)では、libtinfo.so と libtinfo.so.6 といったファイルが存在しましたが、 libtinfo.so.6 は(libtinfo.soではなく)libncursesw.so.6 へのシンボリックリンクとなっており、 また libtinfo.so そのものは、中身が単に「INPUT(libncursesw.so.6)」とだけ書かれたテキストファイルとなっていました。 いずれも「ncurses」という文字列が見られたわけですが、 この libtinfo とは ncurses の別名ではないかと推察できます。

この推察を基に、次に libtinfo や ncurses をキーワードとしてGoogle検索しましょう。 検索の結果、やはりこの libtinfo.so とやらは ncurses と同じものとみなせることがわかりました。 このような手順で分析していきます。 今回のケースでは最終的にlibtinfo.so からlibncursesw.so へのシンボリックリンクを、clang が置いてあるディレクトリ内に張ればよいでしょう。 $HOME/Downloads/llvm_clang_install_dir/bin内で以下のように実行します。

ln -s /usr/lib64/libncursesw.so libtinfo.so.5
ls -al

最後の「ls -al」を実行した結果、 「libtinfo.so.5 -> /usr/lib64/libncursesw.so」などといった項目が表示されることを確認してください。

もしも /usr/lib や /usr/lib64 等に libncursesw.so という名前ときっちり一致するファイルがなければ、 一番近い名前のライブラリへのシンボリックリンクとします (とはいえ少なくとも「ncurses」という文字列が含まれたライブラリでなければならないと思います。 最後の「w」はあってもなくても構いません)。 これがない場合はncurses自体がシステムにインストールされていません。 さすがに ncurses 自体は、準標準ライブラリ的にLinuxではよく使われるものとなりますので 通常の方法で本インストールしておきましょう)。

ここまで終わりましたら、次のようにして「./clang」を実行します。

env LD_LIBRARY_PATH=. ./clang

「clang-3.9: error: not input file」などといったエラーが出れば成功です。 しかしながらもしも依然として同じエラーが出る場合、 おそらく上記の手順のどこかで打ち間違えなどをしています。 あるいは「libtinfo.so.5」ではなく他のライブラリに関するエラーかもしれません。 その場合はそのライブラリについて上記と同様の方法で対処します。

先頭に「env LD_LIBRARY_PATH=.」をつけずに単に「./clang」だけで実行すると、 再び先ほどのエラーが出ると思います(出ない場合はそのままで結構です)。 その場合、次のように実行しておきましょう。

export LD_LIBRARY_PATH=$HOME/Downloads/llvm_clang_install_dir/bin:$LD_LIBRARY_PATH

これで単に「./clang」と入力しても成功するはずです。

最後にPATHを通します。 このままですと、このディレクトリ以外の場所からclangを実行するために 毎回「$HOME/llvm_clang_install_dir/bin/clang」と打ち込まなければなりません。 さすがに面倒ですし、オリジナルの osxcross のビルド方法に忠実に従う場合は、 単にclangと入力して実行可能な状態を前提としますので、 以下を実行してPATHを通しておく必要があります。

export PATH=$HOME/Downloads/llvm_clang_install_dir:$PATH

別のディレクトリに移動し、単に「clang」と入力するだけで先ほどと同様な実行結果となるかどうかを確認してください。 尚、一度ターミナルを閉じた場合は、以下を再度実行しなおす必要があります。

export LD_LIBRARY_PATH=$HOME/Downloads/llvm_clang_install_dir/bin:$LD_LIBRARY_PATH
export PATH=$HOME/Downloads/llvm_clang_install_dir:$PATH

それが面倒である場合はホームディレクトリ直下の.bashrcファイル内に上記二行を記述しておきましょう。

osxcross本体(?)の入手


さて、今度こそosxcrossのビルドができると言いたいところですが、 まだ一番肝心な osxcross 本体を入手しておりません。

「さっそく osxcross 本体を入手しなくっちゃ!」と言いたいところですが、 ここまで散々 osxcross の名を連呼しておいて何ですが、 実はosxcross 本体をダウンロードする必要はありません

必要なのは同じ osxcross のサイト(というよりThomas Pochtrager氏のサイト)で配布されている xar、apple-libtapi、cctools-port(ld64のソースコードを含む)の三つであり、 これらをダウンロードする必要があります。 以下それぞれについて説明します。

  • xar
  • 公式サイトはhttps://github.com/tpoechtrager/xarになります。 ここから xar を入手します。

    画面上方の真ん中よりやや右にある「Code」と書かれた緑色のボタンを押すと、 「Download ZIP」と表示されたプルダウンメニューが出るはずです。 このメニューから「Download ZIP」を選びます。 xar-master.zip という名前のファイルをダウンロードすることができますが、 これが xar の本体です。

    尚、xarに限って言えば、そのバージョンを気にする必要はありません。 下二つに比べればこれはザコキャラです。

  • apple-libtapi
  • 公式サイトはhttps://github.com/tpoechtrager/apple-libtapiになります。 ここから apple-libtapi を入手します。

    ただし入手するバージョンに細心の注意を払ってください! 残念ながら今回に関しては、単純に新しければよいというものでもありません。

    darwin17 のコースで行く場合、1000.10.8を使います(これ以外は不可と考えるくらいでよいです)。 画面左上に「1600.0.11.8」などと書かれたボタンがあると思います(ただし新しいバージョンに更新された場合はこの表示が異なるかもしれません)。 これを押すと「Switch branches/tags」というタイトルのプルダウンメニューが出るはずです。

    このメニューから「1000.10.8」を選ぶと、先ほどのボタンの名前が「1000.10.8」という名前に変わります (そもそもページ自体が新しいものに遷移し、URL文字列の最後もこのバージョンのものに変化しているはずです)。

    画面上方の真ん中よりやや右にある「Code」と書かれた緑色のボタンを押すと、 「Download ZIP」と表示されたプルダウンメニューが出るはずです。 このメニューから「Download ZIP」を選びます。 apple-libtapi-1000.10.8.zip という名前のファイルをダウンロードすることができますが、 これが apple-libtapi(Ver1000.10.8)の本体です。

  • cctools-port
  • 公式サイトはhttps://github.com/tpoechtrager/cctools-portになります。 ここから cctools-port を入手します。

    ただし入手するバージョンに細心の注意を払ってください! 残念ながら今回に関しては、単純に新しければよいというものでもありません。

    darwin17 のコースで行く場合、927.0.2-ld64-450.3を使います(これ以外は不可と考えるくらいでよいです)。 画面左上に「1030.6.3-ld64-956.6」などと書かれたボタンがあると思います(ただし新しいバージョンに更新された場合はこの表示が異なるかもしれません)。 これを押すと「Switch branches/tags」というタイトルのプルダウンメニューが出るはずです。

    このメニューから「927.0.2-ld64-450.3」を選ぶと、先ほどのボタンの名前が「927.0.2-ld64-450.3」という名前に変わります (そもそもページ自体が新しいものに遷移し、URL文字列の最後もこのバージョンのものに変化しているはずです)。

    画面上方の真ん中よりやや右にある「Code」と書かれた緑色のボタンを押すと、 「Download ZIP」と表示されたプルダウンメニューが出るはずです。 このメニューから「Download ZIP」を選びます。 cctools-port-927.0.2-ld64-450.3.zip という名前のファイルをダウンロードすることができますが、 これが cctools-port(cctools Ver 927.0.2 および ld64 Ver 450.3)の本体です。

繰り返しますが上で指定したバージョンのものを組み合わせて使うことが重要です。 それ以外のバージョンの組み合わせでも最終的に上手く行く可能性はゼロではありませんが、 失敗する確率が一気に跳ね上がります。

尚、我々がこの組み合わせを選んだ理由は、 osxcross Ver1.1 の build.sh の91行目から112行目において、次のような記述があることに依拠しています。

## Apple TAPI Library ##

if [ $NEED_TAPI_SUPPORT -eq 1 ]; then
  get_sources https://github.com/tpoechtrager/apple-libtapi.git 1000.10.8

  if [ $f_res -eq 1 ]; then
    pushd $CURRENT_BUILD_PROJECT_NAME &>/dev/null
    INSTALLPREFIX=$TARGET_DIR ./build.sh
    ./install.sh
    popd &>/dev/null
    build_success
  fi
fi

## cctools and ld64 ##

CCTOOLS_VERSION=927.0.2
LINKER_VERSION=450.3

get_sources \
  https://github.com/tpoechtrager/cctools-port.git \
  $CCTOOLS_VERSION-ld64-$LINKER_VERSION


ダウンロードが終わったらxar、apple-libtapi、cctools-port の順にビルドしていきます。

xarのビルド


xar については必要なディスクサイズも約3MBと小さく、 ビルドの時間も10秒ほどといったところで、気にする必要はありません。

ディレクトリ DarwinXC に xar-master.zip が置かれているものとします。

  1. xar-master.zip を解凍し、ディレクトリ xar-master に展開します。

  2. zip ファイルの解凍には以下のように unzip コマンドを用います。

    unzip xar-master.zip
    

    環境によっては unzip コマンドがインストールされていないかもしれません。 その場合は素直に unzip コマンドをインストールするか、7z コマンドなど、 zipを解凍できる他のコマンドがある場合はそれを使ってください。 例えば 7z コマンドで解凍する場合は以下のようになります。

    7z x xar-master.zip
    

    もしかすると unzip ではなく unzzip という名前のコマンドがインストールされているかもしれません。 しかしこの unzzip を使うのは避けた方がよいでしょう。 通常のファイルがディレクトリとして展開されるなどおかしな動作をする場合があります。
  3. ディレクトリ mkfalt_xar 内に入り、シェルスクリプト make_world.sh を以下のように実行します。

  4. THIS_PROJ_DIR=../xar-master sh make_world.sh install
    

    この make_world.sh では xar.mak(GNU Makefileです)を内部で実行します。 すなわち「make -f xar.mak install」に相当するコマンドを実行します (このシェルスクリプトの最後に辺りに書かれているはずです)。

    これを実行すると、ディレクトリ mkfalt_xar の直下にディレクトリ out_dir が自動的に作られますが、 ビルドの結果生成されるオブジェクトファイルやライブラリ、実行バイナリなどは、この out_dir ディレクトリ配下に一時的に置かれます。 またinstall時にこれらのうち必要なものが ../install_dir へコピーされます。

    インストール先はデフォルトで ../install_dir となっていますが、 これを変更したい場合は、環境変数 THIS_INSTALL_DIR で指定を行ってください。 例えば「../../install_dir」に変更したければ以下の通りです。

    THIS_PROJ_DIR=../xar-master THIS_INSTALL_DIR=../../install_dir sh make_world.sh install
    

    あるいは「$HOME/Downloads/install_dir」に変更したければ以下の通りです。

    THIS_PROJ_DIR=../xar-master THIS_INSTALL_DIR=$HOME/Downloads/install_dir sh make_world.sh install
    

    ただし、インストール先を変える場合は、xar、apple-libtapi、cctools-port で同じインストール先に揃えるようにしてください。

    この先で使う mkfalt_cctools-port に含まれる Makefile では xar や apple-libtapi でインストールしたライブラリを使用します。 mkfalt_cctools-port の Makefile では、自分のインストール先と同じ場所に これらのライブラリが置かれているとみなします。 よって、自分のインストール先とこれらのライブラリの置かれた場所が異なる場合、 インクルードヘッダが検出できずにコンパイルエラーが発生するか、 あるいはライブラリ本体を検出できずリンクエラーが発生することになります。
    インストールの結果、xar、apple-libtapi、cctools-port のそれぞれが どのようにインストールされるかを個別に解析する目的に限りますが、 敢えてそれぞれのインストール先を別々にすることもできます。 ただしこれはあくまで解析目的での話です。
    依存ライブラリの位置を引数として指定できるような柔軟性をMakefileに持たせることも可能でしたが、 さすがに指定が煩雑になりすぎると思われたのでそのような仕様にはしていません。 仮にそのようなことがしたい場合、Makefileを直接修正するなどしてください。 このMakefileの可読性は比較的高く、さほど余計なこともしていないので改造は容易だと思います。

    この xar.mak は今ビルドしようとしているプロジェクトの外からでも実行することができるようになっています。 ただしその場合、そのプロジェクトのディレクトリの場所がどこかをこのMakefileに教える必要があり、 その場所を指示しているのが上記の「THIS_PROJ_DIR=../xar-master」の部分になります。

    尚、多くのMakefileがそうであるように、この xar.mak もプロジェクトの中に置いて実行することも可能です。 まずディレクトリ mkfalt_xar をまるごと xar-master の直下に置き、 xar-master/mkfalt_xar に移動した後に xar.mak を実行します。 コマンドにすれば以下のような形になるでしょう。

    cp -r mkfalt_xar xar-master/
    cd xar-master/mkfalt_xar
    make -f xar.mak install
    

    この場合は、環境変数 THIS_PROJ_DIR の指定を省略できます(「THIS_PROJ_DIR=..」が指定されたのと同じです)。

    問題なく完了した場合、一つ上のディレクトリに install_dir という名前のディレクトリが作られ、 その直下にディレクトリ include、ディレクトリ lib、ディレクトリ bin などが作られているはずです。 さらにディレクトリ include 直下にはディレクトリ xar が、 ディレクトリ lib 直下には動的ライブラリ libxar.so などが格納されているはずです。 列挙すると以下のような感じです(他にもシンボリックリンクなどのファイルが少しありますが、 とりあえず以下が確認できればよいでしょう)。

    install_dir/include/xar/xar.h
    install_dir/lib/libxar.so
    install_dir/lib/libxar.a
    install_dir/bin/xar
    

    おめでとうございます! これでまずは xar のインストールが完了しました。 しかしながらここまではただのウォーミングアップです。 ここから先が本番(ボスキャラ)となります。

    この先にいる相手は、残念ながら xar のように易しくはありませんが、 まずはこの xar のビルドをチュートリアルとしてしっかり押えておきましょう。 この先も基本的には、xar と同じやり方でのビルドになるからです。

apple-libtapiのビルド


最大の難関です。 全力でいきましょう。 必要なディスクサイズは約600MB(apple-libtapi-1000.10.8.zipが約80MB、 これを展開すると約460MB、ビルドの結果生成されるファイル群の合計が約60M、計約600MB)、 ビルドに掛かる時間はおよそ25分ほどにもなる巨大な相手です。 準備はよろしいでしょうか?

基本的な手順は xar のときと同じです。 ディレクトリ DarwinXC に apple-libtapi-1000.10.8.zip が置かれているものとします。

  1. apple-libtapi-1000.10.8.zip を解凍し、ディレクトリ apple-libtapi-1000.10.8 に展開します。

  2. unzip apple-libtapi-1000.10.8.zip
    

    展開ディレクトリの名前 apple-libtapi-1000.10.8 が長いと思われるなら、 この名前を変更しても構いません。 その場合、以下の説明で apple-libtapi-1000.10.8 の部分をその名前に読み替えてください。

  3. ディレクトリ mkfalt_apple-libtapi 内に入り、シェルスクリプト make_world.sh を以下のように実行します。

  4. THIS_PROJ_DIR=../apple-libtapi-1000.10.8 sh make_world.sh install
    

    「まずは一旦ビルドできるかどうかを確認すべきで、インストールなど後回しだ」と思われる方は 以下のように二段階で実行してください。

    THIS_PROJ_DIR=../apple-libtapi-1000.10.8 sh make_world.sh
    THIS_PROJ_DIR=../apple-libtapi-1000.10.8 sh make_world.sh install
    

    ディレクトリ mkfalt_apple-libtapi 内には、実に18個ものMakefile(すべてGNU Makefileです)があり、 make_world.sh においては、特に余計な分岐はなく単純に以下の順番でこれを実行します (ただし途中でエラーが発生した場合はそこで停止するようにはしてあります)。

    make -f LLVMSupport.mak
    make -f LLVMDemangle.mak
    make -f LLVMBinaryFormat.mak
    make -f LLVMTableGen.mak
    make -f clang-tblgen.mak
    make -f Basic.mak
    make -f clangBasic.mak
    make -f llvm-tblgen.mak
    make -f llvm_IR.mak
    make -f LLVMBitReader.mak
    make -f LLVMObject.mak
    make -f AttributesCompatFunc.mak
    make -f LLVMCore.mak
    make -f LLVMMC.mak
    make -f LLVMMCParser.mak
    make -f AST.mak
    make -f tapiCore.mak
    make -f libtapi.mak
    

    xar.mak と同じく、これらの Makefile も apple-libtapi を外部からビルドすることができます。 そのため、今回も THIS_PROJ_DIR で apple-libtapi が展開されたディレクトリを指定しています。

    make_world.sh による実行では全体をいくつかのフェーズに分けており、 それぞれのフェーズの開始時にコンパイルオプションなどの情報を提示しつつ一時停止します。 そこで続行するか否かの確認メッセージも出ますので、 ゆっくりと提示された情報を読み、準備がよければEnterキーなど何かキーを押して開始してください。

    この一時停止が不要な場合は make_world.sh の替わりに make_world_yes.sh を使うこともできますが、 初めての場合は、make_world.sh での実行をお勧めします。

    上記を見て何か気づかれたでしょうか? ほとんどの Makefile の名前の先頭が LLVM で始まっています。 また途中で clang-tblgen.mak や clangBasic.mak といった名前のMakefileも見られます。

    今我々は libtapi と呼ばれるライブラリをビルドしようとしていたはずですが、 これではまるで llvm_clang をビルドしているかのようです。 実際その通りで、実は libtapi に含まれるほとんどのファイルは llvm_clang のソースコードです。 llvm_clang は巨大なコンパイラシステムで、そのソースコードも当然巨大なものですが、 libtapi のビルドの難しさは、実のところほぼ llvm_clang のビルドの難しさに起因しています。

    というわけで、llvm_clang のソースコードをビルドした経験を何度もお持ちの方(あまりいないとは思いますが)は、 このミッションはさほど難しく感じないかもしれません。 逆にこれから llvm_clang のソースコードのビルドに挑戦しようとお考えの方(こちらもあまりいないとは思いますが)は、 今回のミッションはよい練習になると思います。

    これらのすべてを詳細に把握しておく必要はありませんが、 「何をやっているのか」「何のためにそれをやっているのか」「それが後でどう使われるのか」などの大まかな流れというか依存関係は把握しておく方がよいでしょう。 その方が万一エラーが発生した場合でも見通しがよくなります。 「困難は分割せよ」が鉄則です。

    • LLVMSupport.mak

    • libLLVMSupport.a という静的ライブラリをビルドします。 これはいわゆる基本的なユーティリティの寄せ集めで、 ビルド中のC++ファイル名からもその働きがおおよそ推察できます (例えば MD5.cpp などはMD5を計算するものでしょう)。

      基本的ということは、役割としては一つ前にビルドした xar に近く、 一番初めの方でビルドしておくべきライブラリということです (znk_projectで言えばlibZnkのようなライブラリに相当します)。

      ただし xar とは異なり、libLLVMSupport は他のライブラリに特には依存していません (一方の xar では liblzma、libbz2、libcrypt、libacl、libxml などを使用しており、 それらに依存しています)。

    • LLVMDemangle.mak

    • libLLVMDemangle.a という静的ライブラリをビルドします。

      Demangle(デマングル)というのは、(C++の世界においては)リンク時に参照する関数名などの変換処理を意味しています。 C++をご存知の方は解説不要でしょうし、C++をご存知でない方もまた知る必要のない情報です。 このライブラリのビルドはすぐに終わります。

    • LLVMBinaryFormat.mak

    • libLLVMBinaryFormat.a という静的ライブラリをビルドします。

      名前からBinaryファイルを扱うためのライブラリと推察できますが、 これだけだと抽象的すぎて何がなんやらわからないと思いますが、 Dwarf.cpp という名前のファイルがあることはわかります。

      DWARF(ドワーフ)とは、一般にはファンタジーに現れる小人で、DQ3ではバーンの抜け道で旅人に嫌がらせ デバッグ用のファイル形式の名前です。 適当に流しておきましょう。 このライブラリのビルドはすぐに終わります。

    • LLVMTableGen.mak

    • libLLVMTableGen.a という静的ライブラリをビルドします。

      第四形態です。 デスピサロと同じくこの形態あたりから攻撃がきつくなってきます。

      何をするものなのか非常に説明しにくいですが、 「C++のヘッダファイルを自動生成するツール」をビルドするための下請けライブラリです。

      何を言ってるのかよくわからないかもしれませんが、 このすぐ後に登場するclang-tblgenというツールをビルドするための材料という理解で結構です。

    • clang-tblgen.mak

    • clang-tblgenというツールの実行バイナリをビルドします。

      このツールは、拡張子が td となっているファイルを、拡張子が inc となっているファイルに変換するためのツールです。 ただしこの拡張子に大して意味はなく、 inc ファイルに至っては、その実体はただの C++ のヘッダファイルです。 こんなものをいつ使うかと思われるかもしれませんが、今でしょ!次に使います。

    • Basic.mak

    • ディレクトリ src/llvm/projects/clang/include/clang/Basic にまとめられた td ファイルを inc ファイルに変換するための Makefile で、 この Makefile においては C/C++コンパイラではなく、直前に作った clang-tblgen というツールで変換を行います。 これらの inc ファイルは mkfalt_apple-libtapi 内の auto_hdr/clang-tbl/clang_Basic/clang/Basic へ生成されます。

      これから先のビルドにあたってこの inc ファイル(ヘッダファイル)をインクルードするソースファイルが登場しますので、 このタイミングで inc ファイルを作っておかなければなりません。

    • clangBasic.mak

    • libclangBasic.a という静的ライブラリをビルドします。

      いわゆる llvm_clang コンパイラの基本となるライブラリです。 なぜ libtapi のビルドに(リンカならともかく)コンパイラの方の基本モジュールが必要なのか段々とよくわからなくなってくると思いますが、 とにかくこれが必要なのでしょう。

      このライブラリはかなりの強敵で、直前のBasicで適切にincファイル(ヘッダファイル)を生成していないと コンパイルに失敗してしまいます。 これらに対するインクルードパスは -I auto_hdr/clang-tbl/clang_Basic となります。

    • llvm-tblgen.mak

    • llvm-tblgenというツールの実行バイナリをビルドします。

      このツールは、拡張子が td となっているファイルを、拡張子が inc または gen となっているファイルに変換するためのツールです。 ただしこの拡張子に大して意味はなく、 inc と gen ファイルに至っては、その実体はただの C++ のヘッダファイルです。 こんなものをいつ使うかと思われるかもしれませんが、今でしょ!次に使います。

      clang-tblgen のときとほぼ同じ説明で一体何が違うのか甚だ疑問ですが、 拡張子をよくご覧下さい。 こちらは inc の他に gen という拡張子のファイルも生成します(ちなみにどちらも実体はC+++のヘッダファイルです)。 なぜわざわざ拡張子を別にしているのかその真意は不明ですが、 中身を見た感じでは、gen ファイルの方は人間が直接書くにはあまりに困難なデータ列が大量に生成されています (おそらく何かのデータテーブルでしょう)。

      またその名前から、clang-tblgen では clang コマンド関係のヘッダを生成する一方、 llvm-tblgen では llvm 関係(とりあえず clang コマンド以外のシステムの部分と考えましょう)のヘッダを生成するということなのでしょう。

    • llvm_IR.mak

    • ディレクトリ src/llvm/include/llvm/IR にある Attribute.td と Intrinsics.td を gen ファイルに変換するための Makefile で、 この Makefile においては C/C++コンパイラではなく、直前に作った llvm-tblgen というツールで変換を行います。 これらの gen ファイルは mkfalt_apple-libtapi 内の auto_hdr/llvm-tbl/llvm_IR へ生成されます。

      これから先のビルドにあたってこの gen ファイル(ヘッダファイル)をインクルードするソースファイルが登場しますので、 このタイミングで gen ファイルを作っておかなければなりません。

    • LLVMBitReader.mak

    • libLLVMBitReader.a という静的ライブラリをビルドします。

      名前だけだと libLLVMBinaryFormat と似ており何が違うのかよくわからない感じですが、 構成ファイルの名前も MetadataLoader.cpp など抽象的でよくわからない感じです。 適当に流しておきましょう。 このライブラリのビルドはすぐに終わります。

      …と言いたいところですが、このライブラリは実は強敵です。 直前のllvm_IRで適切にgenファイル(ヘッダファイル)を生成していないと コンパイルに失敗してしまいます。 これらに対するインクルードパスは -I auto_hdr/llvm-tbl/llvm_IR となります。

    • LLVMObject.mak

    • libLLVMObject.a という静的ライブラリをビルドします。

      Object と付いていますが、いわゆるObject指向プログラミングの意味での Object ではなく、 コンパイラが生成するオブジェクトファイルのことを意味しているのだと思われます。 実際、構成ファイルも COFFObjectFile.cpp や ELFObjectFile.cpp、MachOObjectFile.cpp などといった名前となっています。

      このライブラリも LLVMBitReader と同じく、ビルドにはllvm_IRで生成したgenファイルが必要です。

    • AttributesCompatFunc.mak

    • ディレクトリ src/llvm/lib/IR にある AttributesCompatFunc.td を inc ファイルに変換するための Makefile です。 この Makefile においては C/C++コンパイラではなく、llvm-tblgen で変換を行います。 AttributesCompatFunc.inc ファイルは mkfalt_apple-libtapi 内の auto_hdr/llvm-tbl/AttributesCompatFunc へ生成されます。

      これから先のビルドにあたってこの inc ファイルをインクルードするソースファイルが登場しますので、 このタイミングで作っておかなければなりません。

    • LLVMCore.mak

    • libLLVMCore.a という静的ライブラリをビルドします。

      名前からしてLLVMの心臓部なのでしょうか?

      このライブラリも LLVMBitReader と同じく、ビルドにはllvm_IRで生成したgenファイルが必要です。 また直前で生成したAttributesCompatFunc.incも必要になります。 これらに対するインクルードパスは -I auto_hdr/llvm-tbl/llvm_IR -I auto_hdr/llvm-tbl/AttributesCompatFunc となります。

    • LLVMMC.mak

    • libLLVMMC.a という静的ライブラリをビルドします。

      このMCが何を意味しているのかいまいち不明ですが、LLVM に llvm-mc(LLVM Machine Code)というコマンドがあるようなので 多分それに関するライブラリなのでしょう。 アセンブラに関係する処理だと思います。

      尚、LLVMCoreとは異なり、inc ファイルや gen ファイルなども不要なので、 このライブラリは割とすんなりビルドできるでしょう。

    • LLVMMCParser.mak

    • libLLVMMCParser.a という静的ライブラリをビルドします。

      LLVMMCがアセンブラに関係しているライブラリと思われますので、 このライブラリはそのパーザ処理に相当することをしているのでしょう。 LLVMCoreと同様、特に何かに依存していることもないため、すんなりとビルドできるでしょう。

    • AST.mak

    • ディレクトリ src/llvm/projects/clang/include/clang/AST にまとめられた td ファイルを inc ファイルに変換するための Makefile です。 この Makefile においては C/C++コンパイラではなく、llvm-tblgen で変換を行います。 これらの inc ファイルは mkfalt_apple-libtapi 内の auto_hdr/clang-tbl/clang_AST/clang/AST へ生成されます。

      これから先のビルドにあたってこれらの inc ファイルをインクルードするソースファイルが登場しますので、 このタイミングで作っておかなければなりません。

    • tapiCore.mak

    • libtapiCore.a という静的ライブラリをビルドします。

      さていよいよ大詰めです。 ようやくlibtapi本体関連のビルドとなります。 JSONFile.cpp や YAML.cpp という名前のファイルが見られることから、 tbl ファイル(TAPIを構成するファイル)とは json あるいは yaml に関係したフォーマットではないかと推察されます。

      このライブラリのビルドでは Basic.mak と AST.mak において作った inc ファイルが必要となります。 これらに対するインクルードパスは -I auto_hdr/clang-tbl/clang_Basic -I auto_hdr/clang-tbl/clang_AST となります。

    • libtapi.mak

    • libtapi.so.6.0.1 という動的ライブラリをビルドします。

      長かったですがようやくラストです(apple-libtapiについてのですが)。 ここでビルドするC++のファイルは少ないです。 しかし最後にここまでビルドしてきた静的ライブラリすべてをリンクするという まさしくこれまでの集大成となる処理を行います。

      リンクされる静的ライブラリは10個です。 すなわち、libtapiCore.a libclangBasic.a libLLVMObject.a libLLVMBitReader.a libLLVMCore.a libLLVMMCParser.a libLLVMMC.a libLLVMBinaryFormat.a libLLVMSupport.a libLLVMDemangle.a となります。

      敢えて作成順とほぼ逆順に書きましたが、リンクにおいては 基本的なものほど後ろに持ってくる必要があるため、そのようにしています (libLLVMTableGen.a がありませんが、これについては clang-tblgen と llvm-tblgen のビルドにおいてリンクされており、 既に役目を終えています)。

      またこのMakefileでは、作成した動的ライブラリ libtapi.so.6.0.1 とヘッダファイル (tapi.hを始めとした七つの h ファイルとVersion.incファイル)がインストールされます。 また同時に、libtapi.so.6.0.1 へのシンボリックリンクとして libtapi.so.6 と libtapi.so が作成されます。

      make_world.sh の引数に install を指定した場合、 他のMakefileでは単にビルドのみが行われますが、 この libtapi.mak についてはビルドとインストールの両方が行われます。

  5. 問題なく完了した場合、一つ上のディレクトリに install_dir という名前のディレクトリが作られ、 そこにすべてがインストールされます。

  6. ディレクトリ install_dir の直下には、ディレクトリ include、ディレクトリ lib、ディレクトリ bin などが作られているはずです。 さらにディレクトリ include 直下にはディレクトリ tapi が、 ディレクトリ lib 直下には動的ライブラリ libtapi.so などが格納されているはずです。 列挙すると以下のような感じです(他にもシンボリックリンクなどのファイルが少しありますが、 とりあえず以下が確認できればよいでしょう)。

    install_dir/include/tapi/APIVersion.h
    install_dir/include/tapi/Defines.h
    install_dir/include/tapi/LinkerInterfaceFile.h
    install_dir/include/tapi/PackedVersion32.h
    install_dir/include/tapi/Symbol.h
    install_dir/include/tapi/Version.h
    install_dir/include/tapi/Version.inc
    install_dir/include/tapi/tapi.h
    install_dir/lib/libtapi.so.6.0.1
    install_dir/lib/libtapi.so.6
    install_dir/lib/libtapi.so
    

    長い戦いでしたが、最終的にインストールされるファイルは割とあっさりしています。

    この先、展開ディレクトリ apple-libtapi-1000.10.8 は使いませんので、 ビルドしたライブラリ(libtapi.so.6.0.1)には絶対に何の問題もないという確固たる自信があるだとか、 ディスクサイズの残りのサイズが切迫しておりやむを得ない状況であるならば このタイミングでこの展開ディレクトリを削除することもできます (展開ディレクトリは約460MBもあるので割とバカにならないサイズです)。

    また、mkfalt_apple-libtapi/out_dir 内にあるファイル(こちらは60MB程度になっていると思います)についても ビルドのために一時的に作られたファイルですので削除が可能です。 ディレクトリ out_dir ごと削除してもよいですし、 「sh make_world.sh clean」を実行することでも out_dir の中身が削除されます。

    ただし繰り返しになりますが、削除を行うのはビルドの成功に絶対の自信があるだとか、 ディスクサイズの残りのサイズが切迫している場合だけにしておいた方がよいでしょう。 全部削除してしまうと、後で何か問題があった場合に「部分的な再ビルド」ができなくなる(ゼロから全ビルドをやり直し)になります。 特に apple-libtapi の場合、ゼロから全ビルドをやり直すとまた25分もの時間が掛かることになります。

    尚、当然ですがインストールディレクトリ(install_dir)の方はこの先もまだ使いますので削除してはいけません。

    お疲れ様でした。 これで libtapi のインストールが完了です。 次はいよいよラスボス、リンカld64のビルドです。

cctools-portのビルド


ここでの一番のゴールは リンカld64をビルドすることですが、 これ以外の付属のツールもビルドされます。 またそれに先立って、apple-libtapi の時と同様にいくつかの静的ライブラリもビルドします。

必要なディスクサイズは約50MB(cctools-port-927.0.2-ld64-450.3.zip が約4MB、 これを展開すると約16MB、ビルドの結果生成されるファイル群の合計が約30M、計約50MB)、 ビルドに掛かる時間はおよそ5分ほどです。 apple-libtapi と比べれば大分軽いですが、内部でビルドするものの数自体は多いです。

ld64 のビルドでは、最後に xar(libxar.so)と apple-libtapi(libtapi.so)をリンクします。 最後の最後でこのリンクがうまくいかずエラーが出て止まる可能性もあります。 この場合、xar、apple-libtapi、ld64 のうちどれに問題があるのかを見極めた上で対処しなければなりません。 万が一 xar や apple-libtapi の方に問題がある場合、これらのビルドを部分的にやり直さなければならないかもしれません。 その意味ではやはり cctools-port(というよりld64のビルド)がラスボスと言えるでしょう。

xar と apple-libtapi のビルドおよびインストールは既にうまくいったはずではないかと思われるかもしれませんが、 実際には問題があるのにたまたまエラーが表示されず、うまくいったかのように完了する場合もあります。 例えばコンパイル/リンクオプションの指定や、必要ファイルの指定の不備などです (もっともここでは既にテスト済みのMakefileでビルドを行いますので、 最後までうまくいったなら、通常これらの指定も問題ないと考えてよいとは思います)。

基本的な手順は xar のときと同じです。 ディレクトリ DarwinXC に cctools-port-927.0.2-ld64-450.3.zip が置かれているものとします。

  1. cctools-port-927.0.2-ld64-450.3.zip を解凍し、ディレクトリ cctools-port-927.0.2-ld64-450.3 に展開します。

  2. unzip cctools-port-927.0.2-ld64-450.3.zip
    

    展開ディレクトリの名前 cctools-port-927.0.2-ld64-450.3 が長いと思われるなら、 この名前を変更しても構いません。 その場合、以下の説明で cctools-port-927.0.2-ld64.450.3 の部分をその名前に読み替えてください。

  3. ディレクトリ mkfalt_cctools-port 内に入り、シェルスクリプト make_world.sh を以下のように実行します。

  4. THIS_PROJ_DIR=../cctools-port-927.0.2-ld64-450.3 sh make_world.sh install
    

    「まずは一旦ビルドできるかどうかを確認すべきで、インストールなど後回しだ」と思われる方は 以下のように二段階で実行してください。

    THIS_PROJ_DIR=../cctools-port-927.0.2-ld64-450.3 sh make_world.sh
    THIS_PROJ_DIR=../cctools-port-927.0.2-ld64-450.3 sh make_world.sh install
    

    ディレクトリ mkfalt_cctools-port 内には、実に17個ものMakefile(すべてGNU Makefileです)があり、 make_world.sh においては、特に余計な分岐はなく単純に以下の順番でこれを実行します (ただし途中でエラーが発生した場合はそこで停止するようにはしてあります)。

    make -f libobjc.mak
    make -f libstuff.mak
    make -f ar.mak
    make -f as-arm.mak
    make -f as-i386.mak
    make -f as-x86_64.mak
    make -f as-ppc.mak
    make -f as-ppc64.mak
    make -f as.mak
    make -f misc.mak
    make -f otool.mak
    make -f ld64-libBlocksRuntime.mak
    make -f ld64-libhelper.mak
    make -f ld64-libParsers.mak
    make -f ld64-libPasses.mak
    make -f ld64-ld.mak
    make -f ld64-other.mak
    

    xar.mak と同じく、これらの Makefile は cctools-port の展開ディレクトリの外部からビルドすることができます。 そのため、今回も同じく THIS_PROJ_DIR で cctools-port の展開ディレクトリの位置を指定しています。

    make_world.sh の引数として install を指定した場合、 正確には上記のMakefileに加え、最後に man 関係のファイルのインストールのために install_man.sh が実行されます。 このシェルスクリプトは単に man 関係のファイルをディレクトリ install_dir/share 配下にコピーするものになります。
    cctools-port の場合、apple-libtapi とは異なり、 オリジナルの状態でも cmake とやらは使われておらず、いわゆる GNU のconfigure スクリプトを実行した上でmakeをするという 一般的な構成になっています。 よってこれについては「単にそれを実行してください」で済ませてもよかったのですが、 xar、apple-libtapi、cctools-port のすべてにおいてビルド方法に統一性をもたせるため、 cctools-port についても他と同様のMakefileを用意してあります。

    前半では ar(ライブラリアン)など、各種ツールをビルドします。 後半では ld64 の本体をビルドします。 それぞれのMakefileについて概要を以下に説明しましょう。

    • libobjc.mak

    • libobjc.a という静的ライブラリをビルドします。

      ここでの objc とはプログラミング言語 Objective C のことです。 Objective C は、C++などと同じくC言語を拡張したような言語で、 MacOSXの世界で使用されることが多いです。 そもそもcctools-port はMacOSX用のツールを集めたものですから、ここで Objective C も登場するのでしょう。

      cctools-port の中では一番基本的なライブラリで他のライブラリに特には依存していません。 そのため一番最初にビルドします。

      C言語のソースコードであるため、ビルドも比較的高速ですぐに終わります。

    • libstuff.mak

    • libstuff.a という静的ライブラリをビルドします。

      英語で「stuff」とは「もの」という意味ですね。 読み方は多分「リブスタッフ」ですね。

      live staff

      違う!ソイツじゃない!

      というかデススタッフ(death staff)だ!おまえは!

      確かに「live」と「death」で少し関係しており…いや「live」ではなく「lib」だ!

      あと事務所のスタッフ(staff)とも関係ありません。
      Close


      「もの」とはあまりに漠然とした名前過ぎてて何が何やらわかりませんが、 ライブラリの名前とはそんなものかもしれません。

      znk_projectのライブラリの名前だって冷静に見れば意味がわかりませんからね (libZnkとかlibMoaiとか…)。

      構成ファイルの名前(dylib_root.c ofile.c symbol_list.c など)から考えると、 おそらくMacOSX関係のライブラリやオブジェクトファイルの基本的な取り扱いに関するユーティリティをまとめたものだと思われます。

      C言語のソースコードであるため、ビルドも比較的高速ですぐに終わります。

    • ar.mak

    • ar (ライブラリアン)をビルドします。

      ここに来てようやくar(ライブラリアン)が登場です。

      ar(ライブラリアン)とは(C言語/C++の文脈では)静的ライブラリを作るためのツールです。 いえ、今までも散々静的ライブラリとやらを作ってきたではないかと思われるかもしれませんが、 これはllvm付属のar(ないしはLinux標準に備わったar)を使って作っていたものになります。 一方、これから作る ar はMacOSX(Darwin)用の ar ということになります (ただしLinux上で動作します。ややこしい!)。

      tarというコマンドをご存知かもしれませんがあれとやっていることは本質的に同じで、 静的ライブラリとは複数のオブジェクトファイルを一つのアーカイブ(ARchive)ファイルにまとめたものに他なりません。

      C言語のソースコードであるため、ビルドも比較的高速ですぐに終わります。 このツールのビルドでは、最後にライブラリとして libstuff.a がリンクされます。

      上で「MacOSX(Darwin)用の ar」という言葉を使いましたが、 純粋に ar の機能だけを見た場合、それは単にファイルをアーカイブする処理に過ぎないため、 どの環境でもその働きは変わらないはずです。 ただし ar で作られるアーカイブ(つまり静的ライブラリ)が通常の tar アーカイブとは異なる点として、 シンボル表を持つことが挙げられます。 そのシンボル表を作るためのツールが ranlib です。 このシンボル表については環境毎に異なるため、環境毎に異なるranlibが必要になります。

      「必要なコマンド群」のところでも述べましたが、 cctools-port でビルドされる ar(最終的な名前はx86_64-apple-darwin17-ar)については、 内部で x86_64-apple-darwin17-ranlib が自動的に呼び出されます (ただしSオプションを指定することでこの呼び出しを抑制することもできます)。 よってここで言う「環境によって ar が異なる」というのは、 「ar が内部で自動的に呼び出す ranlib が異なる」ということです。

      ar のソースコード ar.c を見ますと、238行目ほどに下記のような記述が見られ、 少なくとも cctools-port が提供する ar については、 このような自動的な ranlib 呼び出しが確かに存在することがわかります。

      /*
       * The default is to run ranlib(1) for UNIX conformance.  But if the -S
       * option is specified by the user we don't run it.
       */
      if(run_ranlib){
          /* run ranlib -f or -q on the archive */
          reset_execute_list();
          add_execute_list_with_prefix("ranlib");
          if(options & AR_S)
      		add_execute_list("-f");
          else
      		add_execute_list("-q");
          if(toc64)
      		add_execute_list("-toc64");
          add_execute_list(archive);
          if(execute_list(verbose) == 0){
      		(void)fprintf(stderr, "%s: internal ranlib command failed\n",
      		      progname);
      		exit(EXIT_FAILURE);
          }
      }
      

      add_execute_list_with_prefix("ranlib") という関数が呼び出されておりますが、 この関数の実体はライブラリ libstuff のexecute.c 内で定義されています。 また、この execute.c 内でさらに cmd_with_prefix 関数が呼び出され、 引数 "ranlib" の先頭に PROGRAM_PREFIXマクロで定義された文字列が付加され、 この文字列が最終的に実行されるコマンド名となります。

      尚、この PROGRAM_PREFIX マクロについては libstuff をビルドするための Makefile(libstuff.mak) で定義されており、 その値は "$(target_triplet)-" です。 さらにこの変数 target_triplet の値が "x86_64-apple-darwin17" となっています。 これで cctools-port の ar がどのような経路を経て x86_64-apple-darwin17-ranlib を自動的に呼び出すのかが 明らかとなりました。

      尚、x86_64-apple-darwin17-ranlib については後ほど登場する misc.mak でビルドを行います。

    • as-arm.mak
    • as-i386.mak
    • as-x86_64.mak
    • as-ppc.mak
    • as-ppc64.mak
    • as.mak

    • as (アセンブラ)をビルドします。

      C言語のソースコードであるため、ビルドも比較的高速ですぐに終わります。 このツールのビルドでは、最後にライブラリとして libstuff.a がリンクされます。

      as(アセンブラ)とはアセンブリ言語(テキストファイル)をオブジェクトファイルに変換するためのツールです。 ただしMoaiではアセンブラを使いませんので、万一この部分のビルドでエラーが出るなどして止まる場合、 最後の手段としてこの部分のビルドを飛ばしても構いません (make_world.sh の該当する部分(make -f as-arm.mak などを実行している部分)をコメントアウトするなどすればよいでしょう)。

      以下では一応、アセンブラについての簡単な説明と、これらのMakefileについての解説をしておきます。

      アセンブリ言語とは機械語と一対一に対応した命令列が列挙される言語で、 C言語よりもさらに機械語に近いものとなります(というよりあらゆる言語の中で最も機械語に近い言語(機械語自身を除けば)と言ってよいでしょう)。 一般に機械語のファイル(例えばオブジェクトファイルの中身は機械語の部分を含みます)は、 バイナリファイルとなっているため人間が読むのは極めて困難ですが、 アセンブリ言語は(機械語と一対一に対応してはいるものの)それがテキストファイルで表された記述になっているため、 (バイナリファイルを直接読むことに比べれば)人間にとってはるかに読みやすいものになります。

      機械語はCPUの種類によって異なります。 従ってアセンブリ言語の命令列もCPUの種類によって異なり、 一言にアセンブリ言語と言っても、例えばarm版のアセンブリ言語とi386版のアセンブリ言語では、 使われる命令列などが異なります。 それゆえ、arm版のアセンブラやi386版のアセンブラも別々に用意する必要があるわけで、 as-arm.mak は前者をビルドするための Makefile、as-i386.mak は後者をビルドするための Makefile となります。 その他の as-x86_64.mak、as-ppc.mak、as-ppc64.mak も同様で、 それぞれ x86_64版のアセンブラ、ppc(PowerPC)版のアセンブラ、ppc64(PowerPC 64bit)版のアセンブラをビルドするためのMakefileです。

      これらのMakefileの中身をよく見てみると、ビルドに使われているC言語のファイルのほとんどが共通していることがわかります。 つまり同じC言語のファイルをCPUの種類毎に何度もリビルドしていることになり、 一見すると冗長です。しかしさらに注意深くコンパイルフラグを見ると、 これらは同じC言語のファイルに対し異なるマクロを定義してビルドされています。 中でこのマクロがどのように切り分けられてコンパイルされるかはわかりませんので、 ここでは念のため全てをリビルドするようにしてあります(cctools-portに付属していたオリジナルのMakefileでも そのようにビルドするようになっているため、一応それに従ったというのもあります)。

      最後に as.mak というのがありますが、これは上記のMakefileでビルドした全てのアセンブラをオプションによって呼び出すラッパーです。 これのソースファイルは cctools/as/driver.c となりますが、その一番上に書かれている以下の文言が言ってしまえばすべてです。

      /*
       * The assembler driver as and runs the assembler for the "-arch <arch_flag>"
       * (if given) in ../libexec/as/<arch_flag>/as or
       * ../local/libexec/as/<arch_flag>/as.  Or runs the assembler for the host
       * architecture as returned by get_arch_from_host().  The driver only checks to
       * make sure their are not multiple arch_flags and then passes all flags to the
       * assembler it will run.
       */
      

      出だしの「The assembler driver as and runs」は少々解釈し辛い英語なので 「(It is) the assembler driver 'as' and (it) runs」との省略形と読み替えましょう。 この「as」は前置詞のasではなくアセンブラコマンドの「as」です。

      すなわち、コンパイルオプション「-arch」に先ほど説明したCPUの種類<arch_flag>を指定して as コマンドを呼び出した場合、 インストールディレクトリ内の libexec/as/<arch_flag>/as が呼び出されるということです。 <arch_flag> の値は今回の場合、arm、i386、x86_64、ppc、ppc64 のいずれかになります。

    • misc.mak

    • ディレクトリ cctools/misc 内にある各種ツールの実行バイナリをビルドします。

      ここでは以下のツールがビルドされます。

      bitcode_strip   check_dylib       checksyms
      cmpdylib        codesign_allocate ctf_insert
      indr            inout             install_name_tool
      libtool         lipo              nm
      nmedit          pagestuff         ranlib
      redo_prebinding seg_addr_table    seg_hack
      segedit         size              strings
      strip
      

      ここでは基本要件にある ranlib(最終的な名前は x86_64-apple-darwin17-ranlib) が登場しています。 ar と同じく、ranlib も静的ライブラリを作る際に使われるツールです。

      その他、Moaiのビルドにおいて使う可能性があるとすれば、install_name_tool、nm、strip くらいで 正直他のツールはいらないと言えばそうなのですが、ここでは一応全てをビルドする形になります (とはいえ基本的に一つのツールにつき一つのC言語ファイルしかないので大して時間はかかりません)。

      このツールのビルドでは、最後にライブラリとして libstuff.a がリンクされます。

    • otool.mak

    • otool というツールをビルドします(ディレクトリ cctools/otool 内にあります)。

      otool というコマンドはあまり聞きなれないかもしれませんが(実際、Linuxなどでは通常は存在せず、 Darwinにのみ存在するコマンドのようですが)、 MacOSX のオブジェクトファイルであるMach-O形式ファイルの内容をダンプするためのツールです。 Moaiのビルドで直接使うということはありませんが、出来上がったバイナリの妥当性をチェックしたい場合は使用するとよいでしょう。

      C言語のソースコードであるため、ビルドも比較的高速ですぐに終わります。 このツールのビルドでは、最後にライブラリとして libobjc.a と libstuff.a がリンクされます。

    • ld64-libBlocksRuntime.mak

    • libBlocksRuntime.a という静的ライブラリをビルドします。

      ここからはいよいよリンカ ld64 関連のビルドとなります(ディレクトリ cctools/ld64 内のビルドです)。

      このライブラリはメモリ管理やGC(ガベージコレクション)関係の処理を提供するものです。 ソースコードの置かれた位置は少し分かりにくいですが cctools/ld64/src/3rd/BlocksRuntime になります。

      C言語のソースコードであるため、ビルドも比較的高速ですぐに終わります。 cctools-port の中では一番基本的なライブラリで他のライブラリに特には依存していません。

    • ld64-libhelper.mak

    • libhelper.a という静的ライブラリをビルドします。

      このライブラリは文字列処理やクイックソートなど基本的なユーティリティを提供するものです。 ソースコードの置かれた位置は少し分かりにくいですが cctools/ld64/src/3rd になります。

      C言語のソースコードであるため、ビルドも比較的高速ですぐに終わります。 cctools-port の中では一番基本的なライブラリで他のライブラリに特には依存していません。

    • ld64-libParsers.mak

    • libParsers.a という静的ライブラリをビルドします。

      ソースコードの置かれた位置は少し分かりにくいですが cctools/ld64/src/ld/parsers になります。 ここから本格的に ld64 本体のビルドに入り、ソースコードもC言語からC++に変わります。 C++でのビルドの場合、(きちんと測定したわけではないですが体感的には)一つのファイルにつきC言語でのビルドの二倍以上の時間がかかる印象です。

      ライブラリとしてはlibhelperを使用しており、この段階ではまだリンクまではしないものの、 cctools/ld64/src/3rd 配下にあるヘッダファイルがインクルード可能な状態にしておく必要があります。

    • ld64-libPasses.mak

    • libPasses.a という静的ライブラリをビルドします。

      ソースコードの置かれた位置は少し分かりにくいですが cctools/ld64/src/ld/passes になります。

      ライブラリとしてはlibhelperやlibParsersを使用しており、この段階ではまだリンクまではしないものの、 cctools/ld64/src/3rd や cctools/ld64/src/ld/parsers 配下にあるヘッダファイルがインクルード可能な状態にしておく必要があります。

      ここで言うPassとは(これについてはこの記事が想定するレベルを大きく超えますが) リンカの機能を拡張するためのプラグインのようなものを意味しています。

      いわゆるコマンドを実行するためのパス(PATH)のことではありません。
      cctools/ld64/src/passes 配下には様々なパス(プラグインのようなもの)が定義されており、 libPasses とは要するにそのようなプラグインの集合体です (ただしプラグインとは通常動的にロードされるものですが、 これは静的ライブラリとしてリンクされるため、厳密に言えばプラグインとも違います)。

      例えば cctools/ld64/src/passes/objc.h を見ると以下のようになっていることが確認できます。

      #include "Options.h"
      #include "ld.hpp"
      
      
      namespace ld {
      namespace passes {
      namespace objc {
      
      // called by linker to optimize ObjC data structures
      extern void doPass(const Options& opts, ld::Internal& internal);
      
      
      } // namespace objc
      } // namespace passes 
      } // namespace ld 
      

      また cctools/ld64/src/passes/dylibs.h を見ると以下のようになっていることが確認できます。

      #include "Options.h"
      #include "ld.hpp"
      
      
      namespace ld {
      namespace passes {
      namespace dylibs {
      
      // called by linker to optimize use of dylibs
      extern void doPass(const Options& opts, ld::Internal& internal);
      
      
      } // namespace dylibs
      } // namespace passes 
      } // namespace ld 
      

      よく似ています。 というより同じでなければなりません。

      両者はともに(全く同じ関数型からなる)doPass関数を実装しています (中身の実装(cppファイルに記述されておりますが)は勿論異なっています)。 リンカから見れば、これらのdoPass関数に名前空間以外の違いはなく (このことを共通のインターフェースを持つと呼びます)、 つまりリンカ本体のソースコードにおいては、 おそらくこれらの関数の呼び出しを抽象化した形で書いているはずです。 というわけで次はいよいよリンカld64の本体、ldについて見ていきましょう。

    • ld64-ld.mak

    • リンカ ld64 の本体 ld の実行バイナリをビルドします。

      ソースコードの置かれた位置は cctools/ld64/src/ld になります。

      いよいよ大詰め、いわばラスボスの最終形態です。 ここでは今までビルドしてきたlibBlocksRuntime.a、libhelper.a、libParsers.a、libPasses.a をリンクすることになります (libobjc.a や libstuff.a は登場しませんが、これらは他のツールのリンクで使われており、既に役目を終えています)。 それだけではありません。 前の項で苦労してビルドした libxar.so、libtapi.so をついにここでリンクします。 その他、ここでは libLTO(llvm_clangがインストールされているディレクトリ配下にあるライブラリ)や libuuid といった外部ライブラリもリンクします。

      これまで戦ってきた相手と今度は共闘してラスボスを倒すというまさに感動のクライマックスといった感じですが、 物語との違いはこの共闘する仲間が途中で裏切ってこちらが負ける(あるいは詰む)可能性があるということです。

      さて、直前で見た libPasses.a での doPass 関数が ld 本体ではどう呼び出されているのか気になるところです。 本筋とは関係ありませんが、オマケとしてこれを見ておきましょう。 この呼び出しについては cctools/ld64/src/ld/ld.cpp の 1370行目から1390行目あたりに書かれています。 以下がその抜粋です。 一体どれほど巧みな抽象化がなされているのか、期待に胸が膨らみま…

      		// run passes
      		statistics.startPasses = mach_absolute_time();
      		ld::passes::objc::doPass(options, state);
      		ld::passes::stubs::doPass(options, state);
      		ld::passes::huge::doPass(options, state);
      		ld::passes::got::doPass(options, state);
      		ld::passes::tlvp::doPass(options, state);
      		ld::passes::dylibs::doPass(options, state);	// must be after stubs and GOT passes
      		ld::passes::order::doPass(options, state);
      		state.markAtomsOrdered();
      		ld::passes::dedup::doPass(options, state);
      		ld::passes::branch_shim::doPass(options, state);	// must be after stubs
      		ld::passes::branch_island::doPass(options, state);	// must be after stubs and order pass
      		ld::passes::dtrace::doPass(options, state);
      		ld::passes::compact_unwind::doPass(options, state);  // must be after order pass
      #if defined(HAVE_XAR_XAR_H) && defined(LTO_SUPPORT) // ld64-port
      		ld::passes::bitcode_bundle::doPass(options, state);  // must be after dylib
      #endif // HAVE_XAR_XAR_H && LTO_SUPPORT
      
      		// Sort again so that we get the segments in order.
      		state.sortSections();
      		ld::passes::thread_starts::doPass(options, state);  // must be after dylib
      

      まさかの直書ッ!?

      抽象化もへったくれもなく関数テーブルさえ見当たりません! doPass関数を愚直に呼び出して羅列しているだけだとッ!?

      ならば何のために共通のインターフェースで揃えたのでしょうか? (このld以外にもこのdoPass関数を呼び出す謎の存在がいる可能性もありますが、 これ以上の深入りはやめておきましょう)。

      少々拍子抜けする結果となってしまいましたが、 まあ静的リンクする時点でこうなることはある意味必然であったのかもしれません。 あるいは将来的に抽象化し動的ロードに対応するための布石として、 とりあえず現段階でインターフェースを揃えているのかもしれません。

    • ld64-other.mak

    • ディレクトリ cctools/ld64/src/other 内にある各種ツールの実行バイナリをビルドします。

      ここでは以下のツールがビルドされます。

      ObjectDump
      dyldinfo
      unwinddump
      machocheck
      objcimageinfo
      

      ※既にラスボス戦は終了しております。 これらは裏ボスというほどの相手ではありません。 これらはMoaiのビルドに直接必要でもありませんが、一応cctools-portが提供する最後のツールとして Makefileを用意しています。

      これらについては、libxar.so や libtapi.so をリンクすることなくビルドすることができます。 ただしlibBlocksRuntime.a、libhelper.a、libParsers.a をリンクする必要はあります。 また外部ライブラリとして libLTO(llvm_clangがインストールされているディレクトリ配下にあるライブラリ) もリンクする必要があります。

  5. 問題なく完了した場合、xar や apple-libtapi の時に作成したインストールディレクトリ(install_dir)に、 ld64を始めとしたツール群がすべてがインストールされます。

  6. 特にディレクトリ install_dir/bin、install_dir/libexec について確認してください。 すべては列挙しませんが、Moaiのビルドにおいて重要なものは以下です。

    install_dir/bin/x86_64-apple-darwin17-ar
    install_dir/bin/x86_64-apple-darwin17-ranlib
    install_dir/bin/x86_64-apple-darwin17-ld
    

    例えば ar の場合、インストール時に x86_64-apple-darwin17-ar へ改名しつつコピーを行っています。 リンカ ld64 の場合、最終的に x86_64-apple-darwin17-ld という名前になります。

    この先、展開ディレクトリ cctools-port-927.0.2-ld64-450.3 は使いませんので、 このタイミングでこの展開ディレクトリを削除することもできます。

    また、mkfalt_cctools-port/out_dir 内にあるファイルについても ビルドのために一時的に作られたファイルですので削除が可能です。 ディレクトリ out_dir ごと削除してもよいですし、 「sh make_world.sh clean」を実行することでも out_dir の中身が削除されます。

    尚、当然ですがインストールディレクトリ(install_dir)の方はこの先もまだ使いますので削除してはいけません。

    お疲れ様でした。 これですべてのインストールが完了です。 …と言いたいところですが、

    最後の仕上げとしてMacOSX SDKを展開し、所定の位置に置いておく必要があります。 また実際にコンパイルとリンクのテストも必要です。 次はそれについて説明しましょう。

MacOSX SDKの展開とテスト


ディレクトリ DarwinXC に MacOSX10.13.sdk.tar.xz が置かれているものとします。

  1. MacOSX10.13.sdk.tar.xz を解凍し、ディレクトリ MacOSX10.13.sdk に展開します。

  2. よく見ると拡張子が zip ではなく xz というよくわからない形式になっています (「何でこんな変な形式で圧縮しておるのか?」と言いたくもなりますが仕方ありません)。 unzip コマンドで解凍することはおそらくできないでしょう。

    xz 形式を解凍する方法はいくつかありますが、tar コマンドのバージョンが 1.22 以上の場合、それを用いるのが一番よいでしょう (tarのバージョン1.22は2009年3月にリリースされておりますので、よほど古いLinuxでなければバージョンは1.22以上になっていると思います)。 tarのバージョンは以下のようにして確認できます。

    tar --version
    

    これを実行した結果、バージョン番号が 1.22 より大きい数字であれば問題ありません。 以下のように「-J」オプションを利用して実行して解凍しましょう。

    tar -Jxvf MacOSX10.13.sdk.tar.xz
    

    万が一、バージョン番号が 1.22 より小さい場合、 素直にパッケージマネージャー等により新しい tar を本インストール(アップデート)すべきです。 それが一番確実と思われます。

    一応 tar の「-J」オプションに頼らない代替案も示します。

    7z コマンドがある場合、それがバージョン9.04以降である場合に限りますが、xzを解凍することができます (7zのバージョン9.04は2009年5月にリリースされております)。 7z のバージョンは以下のようにして確認できます。

    7z | grep Version
    

    あるいは以下のように i オプションを付けて実行することで xz がサポートされているか否かを直接確認することもできます。

    7z i
    

    サポートされているフォーマットのリストが表示されるはずです。 画面がスクロールして流れてしまう場合は以下のようにgrepで絞り込んで表示してもよいでしょう。

    7z i | grep xz
    
    バージョン番号が 9.04 より大きい数字であれば問題ありません。 これで一応、以下のように実行することで xz を解凍することはできます (しかしこの話にはまだ続きがあります。 特にディスクの残りサイズに余裕のない方はこれの実行は一旦保留してください)。

    7z x MacOSX10.13.sdk.tar.xz
    

    上記で確かに xz形式が解凍されるのですが、拡張子が「tar.xz」となっているため、 解凍された結果は tar ファイルとなります。 この後に tar コマンドでこの tar ファイルを展開すればすべてが終わるのですが、 今回の場合、この tar ファイルが約 570MB にもなります。 さらにこれを展開した場合、その展開ディレクトリのサイズが約 650MB です。 そのため、このように途中で一時的な tar ファイルを作る方法をとると、1.2GB弱ほどの空スペースが必要になります。 例えばディスクの空スペースが残り700MBの場合、本来であれば展開ディレクトリ自体を格納できる空きがあるにもかかわらず、 この一時的な tar ファイルのせいで最後まで解凍することができないことになります。

    そこで(特にディスクサイズに余裕がない場合)このような tar ファイルを作らずに解凍する方法をとる必要があります。

    7z x -so MacOSX10.13.sdk.tar.xz | tar xvf -
    

    7z コマンドの場合、上記のように「-so」オプション(write data stdout)を付加することで 解凍データを標準出力に送ることができます。 それをパイプでtarに流し込むことで、一時的な tar ファイルを作らずに最後まで解凍することが可能になります。

    結局 tar コマンドは使うわけですが、「-J」オプションは使用していないため、 tar のバージョンは何でもよいわけです (とはいえ、7zのバージョンが9.04以降の環境なら、そもそもtarのバージョンも1.22以降である可能性が高く、 tarのバージョンが1.22以降ならば最初から「-J」オプションで解凍すればよいわけではあります)。

    因みに、tar に一切頼らない方法もないわけではありません。 ここまで来ると単なる趣味の領域ですが、最後にそれについて解説しておきましょう。 以下は 7z コマンドのみですべてを完結させる方法です。

    7z x -so MacOSX10.13.sdk.tar.xz | 7z x -si -ttar
    

    パイプより前(xzの解凍)は先ほどと同じです。 パイプより後(tarの展開)について、通常は tar コマンドを使うところを 7z コマンドに置き換えています。 ただし7z コマンドの場合、上記のように「-si」オプション(read data from stdin)を付加することで 解凍データを標準入力として受け取るようにする必要があります。 また、-tオプションにより展開しようとしているアーカイブのタイプを指定します。 今回の場合、アーカイブtarを展開しようとしていますので、その指定は「-ttar」となります。

    その他、xz コマンドというものもありますが(というよりそもそもそれが xz 形式の本家本元であるわけですが)、 このコマンドはちょっと「いわくつき」のもので、直接実行するのはあまりお勧めできません (とはいえ現在は大丈夫であるとは思うのですが)。 またどのみちこのコマンドを利用するにしても、今回の場合 tar コマンドが必要になります。

  3. SDKの展開ディレクトリ MacOSX10.13.sdk をインストールディレクトリ(install_dir)直下に移動します。

  4. 別にSDKの展開ディレクトリはどこに置いても構わないのですが (どのみち、後のリンカのオプション指定においてその場所を明示的に指定しなければなりません)、 ディレクトリ install_dir の直下に置いておく方が次で導入する環境変数ZNK_DARWIN_XC_DIRとの親和性が高いと思いますのでそうしておきます。 以下のように実行しましょう。

    mv MacOSX10.13.sdk install_dir/
    

  5. 環境変数 ZNK_DARWIN_XC_DIR にインストールディレクトリ(install_dir)のパスを設定しておきます。

  6. 別に環境変数を使わずともリンカを実行することは可能ですし、 環境変数を使うにしてもこの名前にする必要はないのですが、 この記事ではこの名前の環境変数でインストールディレクトリ(install_dir)のパスを設定するものとしましょう。 例えば以下のように実行します。

    export ZNK_DARWIN_XC_DIR=$HOME/Downloads/DarwinXC/install_dir
    

    上記のようにセットした場合、 以下のようにある程度抽象化した形でリンカを呼び出すことができます。

    $(ZNK_DARWIN_XC_DIR)/bin/x86_64-apple-darwin17-ld
    

    上記を実行するとおそらく次のように表示されるはずです。

    ld: warning: directory not found for option '-F/System/Library/Frameworks/'
    ld: warning: No version-min specified on command line
    ld: no object files specified
    

    本来は最後のメッセージ「ld: no object files specified」のみが表示されるべきところですが、 それより前にwarningが表示されています。

    一行目の warning は MacOSX SDK の位置を指定していないために表示されています。 これについては「-syslibroot」オプションで指定します。

    「-syslibroot」オプション自体は、今回に限らずクロスコンパイルにおいて重要なオプションです。 例えば標準ライブラリなどをシステムのデフォルトではないものへ切り替える場合等に指定します。

    二行目の warning は -macosx_version_min を指定していないために表示されています。 それではこれらを指定して実行してみます。

    $(ZNK_DARWIN_XC_DIR)/bin/x86_64-apple-darwin17-ld -syslibroot $ZNK_DARWIN_XC_DIR/MacOSX10.13.sdk -macosx_version_min 10.5.0
    

    以下のようなエラーメッセージだけが表示されれば成功です。

    ld: no object files specified
    

    実際のリンクではさらにいくつかのオプションを付けて実行します。 毎回それを打ち込むのも大変ですので、一旦以下のようなシェルスクリプト my_ld64.sh を作っておきましょう。

    #!/bin/sh
    $(ZNK_DARWIN_XC_DIR)/bin/x86_64-apple-darwin17-ld \
    	-demangle \
    	-dynamic \
    	-arch x86_64 \
    	"$@" \
    	-macosx_version_min 10.5.0 \
    	-syslibroot $ZNK_DARWIN_XC_DIR/MacOSX10.13.sdk \
    	-lcrt1.10.5.o \
    	-lSystem \
    

    これを(例えば適当なC言語ファイルtest.cがあったとして)以下のようにして実行します。

    clang -target x86_64-apple-darwin17 -c test.c
    sh my_ld64.sh -o test test.o
    

    シェルスクリプト ld64.sh 内の "$@" の部分はコマンドライン引数に置き換えられると考えましょう。 上記の例では「-o test test.o」がそのままこの部分に置き換わります。

一般的なosxcrossの解説記事では x86_64-apple-darwin17-clang といった名前のファイルを作ることが目的のように書かれている場合が多い(?)ですが、 これは実際には x86_64-apple-darwin17-wrapper と呼ばれるツールへのシンボリックリンクです。

尚、わざわざこのような名前を付けてシンボリックリンクを張っているのには、一応それなりの理由があります。 x86_64-apple-darwin17-wrapper は自分自身のファイル名からデフォルトのオプションを判断し、 処理を分岐します。

例えば x86_64-apple-darwin17-clang と x86_64-apple-darwin17-clang++ はどちらも x86_64-apple-darwin17-wrapper へのシンボリックリンクです。 実体が同じならばどちらを実行しても同じ挙動で実行されるはずであろうと最初は誰もが思うでしょうが、 実はそうはなりません。

x86_64-apple-darwin17-wrapper は自分自身のファイル名(正確にはシンボリックリンク元のファイル名)についている最後の文字列「clang」と「clang++」を参照します。 そしてその文字列の差によってあたかも別のコマンドラインオプションが与えられたかのように C言語用のコンパイルとC++言語のコンパイルを切り替えます (llvm_clang と llvm_clang++ のどちらを内部で呼び出すかを切り替えるということです)。 こんな妙なやり方をせずとも、素直に明示的なオプションを与えることで切り替えた方がよほどわかりやすいと 個人的には思いますが、これはllvm_clangそのものの仕様と整合性をとるという意味でやむを得ない部分もあるのでしょう。

リンカについても似たようなことが起きていますが、今度は llvm_clang そのものもこの「たらい回し」に参加しており、 さらに状況は複雑です。 リンク時においては x86_64-apple-darwin17-wrapper は内部で「-target x86_64-apple-darwin17」や 「-mmacosx-version-min=10.5.0」などといったオプションを付加して llvm_clang を呼び出します(リンカ本体ではなくです)。

即ち、wrapper の中で以下のような呼び出しをしているのと同じです(実際にはまだ少し他のオプションもありますが省略しています)。

clang -target x86_64-apple-darwin17 -mmacosx-version-min=10.5.0 -o test test.o

-targetオプションでは「=」がありませんが、一方で -mmacosx-version-min=10.5.0 では「=」がついているようです。 このことはosxcross/wrapper/main.cpp の255行目あたりで確かめることができます。 そこに定義されたテーブルにおいて、確かに「-mmacosx-version-min」と「=」という文字列が指定されていることがわかります。

実際にターミナルからこのようなコマンドを実行し、どうなるかテストしてもよいでしょう (test.o については、何か適当なC言語のソースファイルtest.cに対し、「clang -target x86_64-apple-darwin17 -c test.c」等を実行し、 予め作っておくとよいでしょう)。

呼び出された llvm_clang(clangコマンド)は、与えられたファイルがオブジェクトファイルであることより、 リンカを呼び出さなければならない状況と判断します。 さらに「-target」オプションで指定された「x86_64-apple-darwin17」という文字列から、 「x86_64-apple-darwin17-ld」という名前のファイル(つまりld64のことですが)を環境変数PATHの設定を参照しつつ探し、 見つかったものをリンカの実体として呼び出します。

このことは llvm_clang のソースコードを直接見ても確かめられます。

あるいはもっと単純に、"hello dummy ld." を表示するだけのプログラムを「x86_64-apple-darwin17-ld」という名前にした上で カレントディレクトリに置き、「PATH=.:$PATH clang test -o test.o」を実行してリンクさせることでも確かめられます (カレントディレクトリ「.」を最優先として環境変数PATHをセットします)。 このとき本来呼び出されるべきリンカの替わりにダミーの「./x86_64-apple-darwin17-ld」が呼び出され、 「"hello dummy ld.」と表示されるはずです。

さらにもう一押ししましょう。この "hello dummy ld." を表示するだけのプログラムを「ld」という名前にした上で 同様にカレントディレクトリに置きます。 「PATH=.:$PATH clang test -o test.o」を実行してリンクさせた場合、 このとき本来呼び出されるべきリンカの替わりにダミーの「./ld」が呼び出され、 やはり「"hello dummy ld.」と表示されることが確認できると思います。

尚、このような名前のファイルが見つからない場合はシステム標準のリンカ「/usr/bin/ld」が実行されますが、 クロスコンパイルの場合、これは明らかに余計なお世話であり、これが実行されてしまうとまず確実にエラーとなるでしょう。 今回の場合、おそらく「unrecognized emulation mode : acosx_version_min」といった旨のエラーが出るはずです。

私のような人間はこのエラーを見て「acosx?数学関係か?」などと思ってしまうわけですが、 このエラーは数学とは関係ありません。 この「acosx」は「macosx」の最初の「m」を取り除いたものです。 しかしなぜこんな妙なことが起きるのでしょうか?

おそらく最初に誰もがやることは x86_64-apple-darwin17-wrapper のソースコードディレクトリ内で 「acosx_version_min」という文字列をキーワードとして grep を掛けることでしょう。 ところがこのような文字列ではどのファイルにもヒットせず、ここで詰まるわけです(私が)。

ここは llvm_clang に「-v」を指定して実行してみることにしましょう。 これにより実際にどのようなコマンドラインでリンカを呼び出しているか、 その真実が明らかとなります。 例えば以下の通りです。

clang -v -target x86_64-apple-darwin17 -mmacosx-version-min=10.5.0 -o test test.o

これを実行すると例えば以下のように表示されます。

/usr/bin/ld -dynamic -arch x86_64 -macosx_version_min 10.5.0 -o test -lcrt1.10.5.o test.o -lSystem -lgcc_s.10.5

何が起きているのか大分わかりやすくなりました。 結局のところ、「-arch x86_64」の指定によりx86_64アーキテクチャ向けの処理が行われ、 また「-lSystem」の指定によりMacOSX10 SDK内のライブラリとのリンクを行うということですね。 「crt1.10.5.o」というオブジェクトファイルもMacOSX10 SDK内に存在することが確認できます。

また元々「-mmacosx-version-min=10.5.0」を指定して llvm_clang を呼び出していたわけですが、 なんと実際の呼び出しではこの部分が「-macosx_version_min 10.5.0」に変化しています。 文字列「-mmacosx-version-min=」が文字列「-macosx_version_min」に変換されているということです (最初のmが一つ減り、「-」が「_」に変換されています)。

この変換は何か?
この奇妙な文字列の変換は何なのでしょうか?

本来これらのオプションを受け取るべき ld64 においては、 確かにここは「-macosx_version_min」と指定しなければならない仕様になっています (「-mmacosx-version-min=」ではなくです。 このことは cctools/ld64/src/ld/Option.cpp 内の記述(例えば3041行目)より確認できます)。

ということは、結局のところ間に入った llvm_clang が、 「-mmacosx-version-min=10.5.0」を受け取って「-macosx_version_min 10.5.0」に変換し、 後ろに続く値「10.5.0」はそのままの状態で ld64 へ渡していたということになります。 涙ぐましい努力です。

まあ、実際にはld64ではなく、/usr/bin/ld に渡しているわけで、最後の一番肝心な所で大ポカしてコケてるわけですが。

これは wrapper の指定の方が変なのでしょうか? いえ、です。 実は llvm_clang の方が「-mmacosx-version-min=」という形でしか受け付けないようになっているのです。 そのため wrapper の方も(llvm_clangを介するなら)このように指定せざるを得ないのです。

まあ「-Wl」オプションを使えば、そのままの文字列をリンカにオプションとして与えられたわけで、 その意味では wrapper の方が最初からそちらを使えばよかっただけの話でもあります。

「たらい回し」をするなら、普通にオプションを「-macosx_version_min」で受け取って そのままの形でld64に渡せばよいのです。 ところが llvm_clangは「たらい回し」の分際で これをあたかも外側からは「-mmacosx-version-min=」というオプションであるかのように勝手に再定義してしまっていることになります。

このことは「clang --help」を実行することで確認できます。 大量のオプションがスクロールで流れるため、普段はあまり詳しく見る気になりませんが、 頑張ってこの「-mmacosx-version-min」というオプションを真面目に探しますと、確かにリストに存在することがわかります。 一方で「-macosx_version_min」というオプションは存在しないこともわかります。

llvm_clang はなぜこんな変換をしているのでしょうか? 「m」を二つ重ねるところは区分上仕方ないかもしれませんが、「_」から「-」への変換は何なのでしょう? 「_」を使うのが気に食わなかったのでしょうか? 「-」を使う主義なんでしょうか? なんでわざわざ「=」足したん?

ところがさらによく調べてみると gcc にも同様のオプション「-mmacosx-version-min=」があることがわかります。 つまり gcc の指定に合わせたということでしょうか? ということは gcc が元凶ですかね?

Close


さて、繰り返しになりますが、llvm_clang が内部で「-macosx_version_min 10.5.0」という文字列に変換し、 システム標準のリンカ「/usr/bin/ld」を呼び出している状況なわけですが、 「-macosx_version_min」というオプションは ld64 固有のものであり、「/usr/bin/ld」には備わっていないオプションとなります。 ただし「/usr/bin/ld」では「-m」オプションが備わっており、 そのため「-macosx_version_min」という指定は「-m」オプションとみなされます。 結果的に「/usr/bin/ld」において「acosx_version_min など知らん」といったエラーが出ていたということですね。

これは実際に与えたオプション文字列と(特に「_」と「-」において)食い違っており、 このエラーを見る側からすれば尚更混乱してしまうような状況になっています。

何ともややこしいことが起きていますが、これならもういっそのこと間に wrapper や llvm_clang など挟まず、 実体である ld64 を直接呼び出せばいかがでしょうか? それが一番シンプルです。その方が余計なトラブルを減らし、変なポイントで迷わずに済みます。

勿論、リンカのオプションを適切に指定する必要はありますが、 この場合、リンカのオプションだけを考えればよいのです。 つまり考慮しなけばならない範囲を局所化できるということです。

一方、llvm_clang コマンド一つで、コンパイルもリンクも全部済まそうという試みは、 聞こえはよいかもしれませんが、llvm_clang に存在する大量のオプションの中にリンカのオプションも紛れ込むということでもあります。 それでもうまく抽象化できていればまだよいのですが、例えば target triplet 一つとっても現実の状況は混沌としています。 現状はまるでグローバルスコープにグローバル変数を宣言する(しかもそのグローバル変数が大量にある)ような状況ではないでしょうか? しかもこれを使って内部でよくわからない変換がユーザが見えにくい形で行われ、しかも環境変数PATHの設定の仕方だけで たとえばリンカの認識に結局失敗するわけであります (それならもう環境変数PATHを設定した上でリンカの実体(本名)を直接呼び出す方が確実でしょう)。

というわけでこの記事で説明しているやり方も x86_64-apple-darwin17-wrapper 等のラッパーを経由せず、 実体を直接呼び出すというものになっています。

Moai自体のクロスコンパイルおよびインストール


ようやくツールのビルドが終わりましたので、Moaiのクロスコンパイルに入れます。 これについてはツールのビルドがきちんと成功しているなら、全く難しくありません。

  1. Darwin ターミナルを開き、インストール先のパスを環境変数 ZNK_INSTALL_DIR で指定します。

  2. export ZNK_INSTALL_DIR=$HOME/install_dir
    

    環境変数 ZNK_INSTALL_DIR で指定されたディレクトリが存在しない場合は自動的に生成されます。 Moaiの場合、$ZNK_INSTALL_DIR/moai-v$REL_VER($REL_VERはMoaiのバージョン番号で例えば2.3など)という名前のディレクトリが生成され、 必要なファイルがそのディレクトリ配下へとインストールされます。

    特にコンパイルされた実行バイナリやライブラリなどは、$ZNK_INSTALL_DIR/moai-v$REL_VER/bin/$PLATFORM という名前のディレクトリに配置されます。 ここで $PLATFORM は実行バイナリの種類を表し、例えばDarwinにおいては、darwin17-i386、darwin17-x86_64 といった文字列になります。 Rarakuの場合も同様で、$ZNK_INSTALL_DIR/raraku-v$RRK_VER($RRK_VERはRarakuのバージョン番号で例えば1.0など)という名前のディレクトリ配下にインストールされます。

    尚、環境変数 ZNK_INSTALL_DIR が指定されていない場合は、src ディレクトリの一つ上の階層に install_dir というディレクトリが生成され、 その配下に moai-v$REL_VER や raraku-v$RRK_VER などが配置されます。

  3. llvm_clangのインストール先を環境変数 ZNK_LLVM_CLANG_DIR で指定します。

  4. このllvm_clangはxar、libtapi、ld64をビルドした際に使用したものと同じもので構いません。 これらをビルドして引き続きMoaiのビルドに入るなら、この環境変数は既に設定しているはずですので 何もする必要はありません。

    Makefile_darwin.mak は環境変数 ZNK_LLVM_CLANG_DIR が定義されている場合、 llvm_clang を使用したビルドに自動的に切り替えるようになっています。 そのため、darwin17の場合、この環境変数 ZNK_LLVM_CLANG_DIR の定義は必須です。 たとえ単に「clang」と打ってllvm_clangが実行できるようになっていたとしてもです。

  5. xar、libtapi、ld64のインストール先を環境変数 ZNK_DARWIN_XC_DIR で指定します。

  6. llvm_clangはxar、libtapi、ld64をビルドして引き続きMoaiのビルドに入るなら、 この環境変数は既に設定しているはずですので 何もする必要はありません。

    Makefile_darwin.mak は環境変数 ZNK_DARWIN_XC_DIR が定義されている場合、 darwin向けのクロスコンパイルに自動的に切り替えるようになっています。

  7. srcディレクトリへ移動し、以下のシェルスクリプトを実行します。

  8. cd znk_project/src
    sh ./make_world.sh install
    

    これですべてのコンパイルおよびインストールが自動で行われます。

    このシェルスクリプトにより、Makefile_darwin.mak の存在する各ディレクトリへ自動的に移動しつつ make -f Makefile_darwin.mak install が実行されます。 コンパイル後の実行バイナリは各ディレクトリ内のout_dirに格納されます。

以上でMoaiのコンパイルとインストールはすべて完了です。

尚、当然ですが、出来上がったものはDarwin用のバイナリであるため、 Linux上ではこれの動作確認は行えません。 なんらかのエミュレータが必要です。

ただしLinux上でも file コマンドを使用することで、出来上がったファイルがDarwin用のバイナリであるかどうかの 最低限の確認はできます。

osxcross がオリジナルで用意している方法(注意点)


この記事では osxcross がオリジナルで用意している方法ではなく、 別の方法でビルドを行いました。

まあどんな方法であれ、ビルドできてしまえば話はこれで終わりなのですが、 最後に一応、osxcross がオリジナルで用意している方法についても説明しておきます。 その概要は以下の通りです。

osxcrossでのビルドの概要:

今、osxcross 本体のディレクトリを osxcross としましょう。 osxcross がオリジナルで用意している方法とは、 osxcross 本体に付属するビルドスクリプト(osxcross/build.sh)を実行することです。 このスクリプト一つですべての処理が一括で行われます (正確には osxcross/tools/tools.sh など下請けのスクリプトも中で呼び出されます)。

ただし、MacOSX SDK についてだけは、その圧縮アーカイブを(手動で)入手しておく必要があり、 またそれをディレクトリ osxcross/tarballs 内に(手動で)置いておく必要があります。

ここまで配備しておけば、ユーザは単に osxcross/build.sh を実行するだけでよく、 このスクリプトが最後まで問題なく実行できればそこですべて完了となります。 ディレクトリ osxcross/target がインストールディレクトリに相当し、そこに最終的な生成物 (クロスコンパイル用のコンパイラ、リンカ、ライブラリアン、展開されたSDKディレクトリなど)が作られているはずです。

このスクリプト(osxcross/build.sh)の実行においては、一時作業領域としてディレクトリ osxcross/build が生成されます (我々がとった方法で言えば out_dir ディレクトリに相当するものです)。 この中でさらに ディレクトリ osxcross/build/xar、 ディレクトリ osxcross/build/apple-libtapi、 ディレクトリ osxcross/build/cctools-port が自動的に生成されます。 このとき、xar、apple-libtapi、cctools-port についても自動でダウンロードされます。 (内部でgitコマンドが呼び出されます)。

ただし我々がこの方法をとることを回避したのはそれなりに理由があります。 一旦その理由と実際にビルドする場合の注意点などをお話しします。 その後で実際の手順も説明しましょう。

さて、まず第一の問題点ですが、 この osxcross/build.sh は途中で cmake とやらを呼び出します(というよりオリジナルのapple-libtapi自体が デフォルトでは cmake によるビルドを行う構成になっており、こちらで makefile を用意するなど対処しない限り 必然的に cmake を使うハメになってしまいます)。 つまり別途 cmake のインストールが必要となってしまい、結局こちらの方が準備も手間な上、 ディスクサイズを消費します。

cmake は初めて聞くとその名前から make の代替としての役目をする(GNU makeのパワーアップ版のような)ツールのように思われるかもしれませんが、 そうではなく単にGNU Makefile を生成するだけのツールであり、いわゆる GNU configure のような処理を行います。

これだけでも若干ネーミング詐欺感があるというか、怪しさが漂いますが…。

この configure に相当する処理で無駄に本質でないエラーを出し、無駄な対処を迫られる可能性まであります。 osxcross/build.shを使用した場合、これが問答無用で毎回実行されます。 最初の一回ならまだしも、こんなものを同じ環境で毎回何度も行われてはたまりません。

またこの処理により2、3個のヘッダファイルも生成されますがどのみちそこに大した情報は含まれていません。 あるいはよほど変な環境で生成しない限り、最近のLinuxならどれでも同じ内容となるようなものです (そもそもosxcrossが実行できる環境は限定されており、どのみちこの部分で環境毎に細かなヘッダを用意する意味はありません)。 この記事で述べる方法では、そのヘッダファイルをあらかじめ作って用意してあります。
cmakeのツール本体のサイズは50MBです(まあこれはよいとしましょう)。 ところが cmake が一つのプロジェクト内に生成するMakefile群の合計サイズですが、なんとこれが50MBほどにもなります

生成されるオブジェクトファイル、ライブラリのようなバイナリを含めて50MBというならまだわかります。 そうではなく、そういうものを除いた純粋にMakefile群やビルドに必要なcmake系の設定ファイル(テキストファイル)だけで50MBということです。

例えば、n 個のプロジェクトでcmakeをを使った場合、単純に掛け算をして見積もると、 ただのMakefile群だけで 50n MBにもなるということですか?(吐血)。

それにしてもMakefile群だけで 50MB というのは恐ろしく異常です。 生成されるMakefile群はどれも異常な行数なのですが、 今回の場合、「osxcross/build/apple-libtapi/build/CMakeFiles/Makefile2」というMakefileファイルが特に大作で、 なんと37000行もあります! もはや Makefile2 ではなく Makefile37000 とでも呼ぶべき恐るべき存在です。 しかも中身は他のMakefileを呼び出すだけというラッパーであり、なんと実質無意味です。 ここまで来ると逆に大したものです。

apple-libtapiは特に巨大なライブラリであるため、このようなとんでもないサイズになるのでしょうか? いえ、一概にそれだけが理由とは言えません。

我々が用意したapple-libtapi用の Makefile群の合計サイズは精々0.5MB(500KB)ほどです (行数も長いものでさえ250行程度です)。 実に 1/100 のサイズで済むというなんとも凄まじい比率ですが、我々が作ったMakefileが特別小さいのかと言われると 別にそんなことはなく誰もが同様に書くであろうオーソドックスなMakefileです。 GNU configureが生成するMakefileファイル(あれもあまり褒められた内容ではないですが)でさえ、通常その倍くらいのサイズ (1MB程度)で済みます。 要するに cmake が生成するMakefileのサイズだけが明らかに異常なのです。

なぜ cmake はこれほどまでに異常に巨大なサイズの Makefile(と呼ぶにも憚れる物体)を吐くのでしょうか? 中で何をやってるのでしょうか? 気になる方はcmakeの吐くMakefileを診断した結果が以下にありますのでご覧下さい。 cmakeについてはこれくらいにしておきましょう。 こんなものにあまり深入りしている場合ではありません(cmakeのインストールについては後で述べます)。

診断結果
ご臨終です。

以下は cmake の吐いたMakefileの死亡診断書です(検視官は私Zenkakuとなります)。 長いですので二部構成に致します。

Makefile2(通称Makefile37000)に関する死亡診断書
いくらなんでも37000行ものコード全体をここに載せるわけにはいきませんので、 ここではサンプルとして LLVMSupport に関するターゲットが記述された箇所のみを以下に抜粋します。

#=============================================================================
# Directory level rules for directory lib/Support

# Convenience name for "all" pass in the directory.
lib/Support/all: lib/Support/CMakeFiles/LLVMSupport.dir/all

.PHONY : lib/Support/all

# Convenience name for "clean" pass in the directory.
lib/Support/clean: lib/Support/CMakeFiles/install-LLVMSupport-stripped.dir/clean
lib/Support/clean: lib/Support/CMakeFiles/install-LLVMSupport.dir/clean
lib/Support/clean: lib/Support/CMakeFiles/LLVMSupport.dir/clean

.PHONY : lib/Support/clean

# Convenience name for "preinstall" pass in the directory.
lib/Support/preinstall:

.PHONY : lib/Support/preinstall

#=============================================================================
# Target rules for target lib/Support/CMakeFiles/install-LLVMSupport-stripped.dir

# All Build rule for target.
lib/Support/CMakeFiles/install-LLVMSupport-stripped.dir/all: lib/Support/CMakeFiles/LLVMSupport.dir/all
	$(MAKE) -f lib/Support/CMakeFiles/install-LLVMSupport-stripped.dir/build.make lib/Support/CMakeFiles/install-LLVMSupport-stripped.dir/depend
	$(MAKE) -f lib/Support/CMakeFiles/install-LLVMSupport-stripped.dir/build.make lib/Support/CMakeFiles/install-LLVMSupport-stripped.dir/build
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --progress-dir=/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles --progress-num= "Built target install-LLVMSupport-stripped"
.PHONY : lib/Support/CMakeFiles/install-LLVMSupport-stripped.dir/all

# Build rule for subdir invocation for target.
lib/Support/CMakeFiles/install-LLVMSupport-stripped.dir/rule: cmake_check_build_system
	$(CMAKE_COMMAND) -E cmake_progress_start /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles 4
	$(MAKE) -f CMakeFiles/Makefile2 lib/Support/CMakeFiles/install-LLVMSupport-stripped.dir/all
	$(CMAKE_COMMAND) -E cmake_progress_start /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles 0
.PHONY : lib/Support/CMakeFiles/install-LLVMSupport-stripped.dir/rule

# Convenience name for target.
install-LLVMSupport-stripped: lib/Support/CMakeFiles/install-LLVMSupport-stripped.dir/rule

.PHONY : install-LLVMSupport-stripped

# clean rule for target.
lib/Support/CMakeFiles/install-LLVMSupport-stripped.dir/clean:
	$(MAKE) -f lib/Support/CMakeFiles/install-LLVMSupport-stripped.dir/build.make lib/Support/CMakeFiles/install-LLVMSupport-stripped.dir/clean
.PHONY : lib/Support/CMakeFiles/install-LLVMSupport-stripped.dir/clean

# clean rule for target.
clean: lib/Support/CMakeFiles/install-LLVMSupport-stripped.dir/clean

.PHONY : clean

#=============================================================================
# Target rules for target lib/Support/CMakeFiles/install-LLVMSupport.dir

# All Build rule for target.
lib/Support/CMakeFiles/install-LLVMSupport.dir/all: lib/Support/CMakeFiles/LLVMSupport.dir/all
	$(MAKE) -f lib/Support/CMakeFiles/install-LLVMSupport.dir/build.make lib/Support/CMakeFiles/install-LLVMSupport.dir/depend
	$(MAKE) -f lib/Support/CMakeFiles/install-LLVMSupport.dir/build.make lib/Support/CMakeFiles/install-LLVMSupport.dir/build
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --progress-dir=/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles --progress-num= "Built target install-LLVMSupport"
.PHONY : lib/Support/CMakeFiles/install-LLVMSupport.dir/all

# Build rule for subdir invocation for target.
lib/Support/CMakeFiles/install-LLVMSupport.dir/rule: cmake_check_build_system
	$(CMAKE_COMMAND) -E cmake_progress_start /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles 4
	$(MAKE) -f CMakeFiles/Makefile2 lib/Support/CMakeFiles/install-LLVMSupport.dir/all
	$(CMAKE_COMMAND) -E cmake_progress_start /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles 0
.PHONY : lib/Support/CMakeFiles/install-LLVMSupport.dir/rule

# Convenience name for target.
install-LLVMSupport: lib/Support/CMakeFiles/install-LLVMSupport.dir/rule

.PHONY : install-LLVMSupport

# clean rule for target.
lib/Support/CMakeFiles/install-LLVMSupport.dir/clean:
	$(MAKE) -f lib/Support/CMakeFiles/install-LLVMSupport.dir/build.make lib/Support/CMakeFiles/install-LLVMSupport.dir/clean
.PHONY : lib/Support/CMakeFiles/install-LLVMSupport.dir/clean

# clean rule for target.
clean: lib/Support/CMakeFiles/install-LLVMSupport.dir/clean

.PHONY : clean

#=============================================================================
# Target rules for target lib/Support/CMakeFiles/LLVMSupport.dir

# All Build rule for target.
lib/Support/CMakeFiles/LLVMSupport.dir/all:
	$(MAKE) -f lib/Support/CMakeFiles/LLVMSupport.dir/build.make lib/Support/CMakeFiles/LLVMSupport.dir/depend
	$(MAKE) -f lib/Support/CMakeFiles/LLVMSupport.dir/build.make lib/Support/CMakeFiles/LLVMSupport.dir/build
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --progress-dir=/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles --progress-num=48,49,50,51 "Built target LLVMSupport"
.PHONY : lib/Support/CMakeFiles/LLVMSupport.dir/all

# Include target in all.
all: lib/Support/CMakeFiles/LLVMSupport.dir/all

.PHONY : all

# Build rule for subdir invocation for target.
lib/Support/CMakeFiles/LLVMSupport.dir/rule: cmake_check_build_system
	$(CMAKE_COMMAND) -E cmake_progress_start /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles 4
	$(MAKE) -f CMakeFiles/Makefile2 lib/Support/CMakeFiles/LLVMSupport.dir/all
	$(CMAKE_COMMAND) -E cmake_progress_start /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles 0
.PHONY : lib/Support/CMakeFiles/LLVMSupport.dir/rule

# Convenience name for target.
LLVMSupport: lib/Support/CMakeFiles/LLVMSupport.dir/rule

.PHONY : LLVMSupport

# clean rule for target.
lib/Support/CMakeFiles/LLVMSupport.dir/clean:
	$(MAKE) -f lib/Support/CMakeFiles/LLVMSupport.dir/build.make lib/Support/CMakeFiles/LLVMSupport.dir/clean
.PHONY : lib/Support/CMakeFiles/LLVMSupport.dir/clean

# clean rule for target.
clean: lib/Support/CMakeFiles/LLVMSupport.dir/clean

.PHONY : clean

なんと「LLVMSupportをビルドするための Makefile を呼び出すだけの処理だけ」で120行もあります! 繰り返しますが、ここでやっていることは実質何もなく、 「LLVMSupportのビルドを行うための Makefile(実体はlib/Support/CMakeFiles/LLVMSupport.dir/build.make)を 単に呼び出すだけ」です。 極端な話、1 行で済む(いえ、そもそもコマンドで直接実体の build.make を実行すれば 0 行で済む)というものです。

また気になるのがこのほぼディレクトリの構造に依拠した長いターゲット名です。 これをどう使えと言うのでしょうか? 以下のように呼び出せとでも言うのでしょうか?

make -f Makefile2 lib/Support/CMakeFiles/LLVMSupport.dir/all

しかしここまでディレクトリのパスをズラズラ並べるなら 実体のMakefileへのディレクトリのパスを直接打ち込んで実行するのと同じようなものでしょう! Makefile2 を介さず、以下のように直接呼び出せばよいだけではないでしょうか?

make -f lib/Support/CMakeFiles/LLVMSupport.dir/build.make

実は前段階として、この Makefile2 を呼び出している Makefile がさらに存在しており(こちらのMakefileも10000行はあるという中々の変死体大物です)、 実際そちらではこれをどう呼び出しているのかを確認致しましたところ、 以下のようになっています(変数MAKEの内容はmakeと考えて頂いて結構です)。

# build rule for target.
LLVMSupport: cmake_check_build_system
	$(MAKE) -f CMakeFiles/Makefile2 LLVMSupport
.PHONY : LLVMSupport

なんとこの長ったらしいターゲット名は実際には(外部で)使われていないではないですか!

改めて上記の Makefiles2 のコードをよく見直してみますと、 LLVMSupportターゲット(短い名前のターゲット)も別途定義されています。 該当部分を以下に抜粋しましょう。

# Build rule for subdir invocation for target.
lib/Support/CMakeFiles/LLVMSupport.dir/rule: cmake_check_build_system
	$(CMAKE_COMMAND) -E cmake_progress_start /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles 4
	$(MAKE) -f CMakeFiles/Makefile2 lib/Support/CMakeFiles/LLVMSupport.dir/all
	$(CMAKE_COMMAND) -E cmake_progress_start /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles 0
.PHONY : lib/Support/CMakeFiles/LLVMSupport.dir/rule

# Convenience name for target.
LLVMSupport: lib/Support/CMakeFiles/LLVMSupport.dir/rule

.PHONY : LLVMSupport

上記によると、Makefile2 の実行において LLVMSupportをターゲットとして呼び出した場合、 それは「lib/Support/CMakeFiles/LLVMSupport.dir/rule」をターゲットとしたのと同じになります。 さらにそのすぐ上を見ると、この「lib/Support/CMakeFiles/LLVMSupport.dir/rule」が行うべき仕事が書かれており、 その中で「$(MAKE) -f CMakeFiles/Makefile2 lib/Support/CMakeFiles/LLVMSupport.dir/all」を実行しています。 これは結局、自分自身(Makefile2)を再び実行しており、 ただし今度はターゲットを「lib/Support/CMakeFiles/LLVMSupport.dir/all」に変更しているということです。

整理しましょう。 ここまででターゲットは以下のように(無駄に)遷移しました。

LLVMSupport
→ lib/Support/CMakeFiles/LLVMSupport.dir/rule
→ lib/Support/CMakeFiles/LLVMSupport.dir/all

何なのですか!?この無意味なたらい回しの連続技は! 何かの抽象化をしているのでしょうか(しかし特に意味がある抽象化とも思えません)。

因みにですが、この変数CMAKE_COMMANDの内容はcmakeです。 つまり生成されたこのGNU Makefile は「GNU makeコマンド単独で実行できる(GNU makeコマンドだけで完結する)Makefile」というわけではなく、 (これだけ大量のコードを生成しておきながら)結局 cmake コマンドの存在に依存するのです。

いえ、「外部のコマンドに依存する Makefile」というのは、それ自体は普通のことです。 コンパイラやリンカとてMakefileから見れば外部のコマンドですからね。 しかし、ここでの cmake コマンドは別にビルドに関して本質的な仕事はしておらず、 いわゆるcmakeが提供するプログレス表示(今何%まで進んだかの表示)のためだけに呼ばれています。 これが cmake 自慢の「お家芸」なのでしょうが、 このプログレス表示もさほど正確ではなく、申し訳ないですが不要です (100%まで終わったかと思えばまた0%に数字が戻ります。何ですかこれは!意味がわかりません)。

おそらくある母集団のビルドが100%になった瞬間、次の母集団に移ってまた 0% になっているのでしょう。 見てる側からすれば数字だけしか表示されていないため、終わったかと思ったら終わっていない(ふりだしに戻されるような) 感覚になってしまうのです。 これをやるなら何に対する「%」なのかを明確に表示すべきでしょう。
ダウンロードツールや、アーカイブを解凍するツールであれば「%」表示するのもよいと思います。 あれば全体のバイト数に対して処理済みのほぼ正確なバイト数がわかるため、 計算される割合もほぼ正確となります。例えば 7z などで解凍時に表示される%表示は非常によいと思います。

しかしながらこのようなビルド処理の場合、個々の処理の種類や長さは一様ではなく、 従って正確な割合を出すことなど土台無理なのです。 例えばMicrosoftのインストーラなどでもそうですが、この手のツールでの単に漠然とした「%」の表示や まして「残り時間何秒」だとかの表示が正確だった試しがありません。

このような場合、単純な%表示などむしろ避けるべきです。 そうではなく今ビルド中のライブラリ名などを率直に表示すべきです。 どうしても割合的なものを出したければ、 「今ビルドしようとしているライブラリが何番目のものなのか」を分子、 「プロジェクト全体で作らなければならないライブラリの総数」を分母として分数で表した方がまだマシです。 変に百分率にしてボカすべきではありません。

またこういうものの割合を出す粒度として、各ソースファイルごとに細かく考える必要はありません。 例えば上で述べたライブラリごとといったように大まかな粒度でよいのです。 各ライブラリでの細かな進捗は、今現在コンパイルされているファイル名を見ればわかります。 なぜならソースファイルは通常アルファベット順など何らかの規則に従ってソートされた状態でビルドされるからです。 例えばライブラリ L において、「L/Archive.c」をビルド中なら、(頭文字がAですから)まだそのライブラリLのビルドは出だしであることはわかりますし、 「L/X86Machine.c」をビルド中なら、(頭文字がXですから)もうそろそろライブラリLのビルドは終わりであろうこともわかります。

さて、結局このようなものが、まるでソースコード全体を再帰的にリストした(ls -Rコマンドを実行した結果のような)並びになって 延々と羅列されています。それが37000行にもなっている原因です。

例えばこのようなもの(120行)が300個並べば36000行くらいにはなりますね。 つまりapple-libtapi内には実際にビルドを行うためのMakefileファイルが300個ほどあるのでしょう。

しかしここで鋭い方は気づかれたかもしれません。 我々が用意したapple-libtapiに関するMakefileの個数は18個です。 つまり残り280個ほどのMakefileはスルーされているのではないか?と。 見事正解です。

既に述べたようにapple-libtapiのソースコードのほとんどはllvm_clangのソースコードです。 言い換えれば llvm_clangのソースコードを元に作られているとも言えます。 ところが apple-libtapi は llvm_clang に含まれるすべてのライブラリを使うわけではなく、その一部だけを使っています。 その他の大部分はllvm_clangのソースコードの名残で残っているだけであり、なんと未使用なのです。 この未使用な部分に関する記述も Makefile2 には記述されているため、尚のこと巨大な死体になっているのです。

ではapple-libtapiで実際に使われている(アクティブな)ライブラリはどれなのでしょうか? そしてどうやればそれを知ることができるのでしょうか? まずは apple-libtapi内にあるbuild.sh の以下の記述に注目します。

echo ""
echo "## Building clangBasic ##"
echo ""

$MAKE clangBasic -j $JOBS

echo ""
echo "## Building libtapi ##"
echo ""

$MAKE libtapi -j $JOBS

clangBasic をターゲットにして Makefile を実行しておりますので libclangBasic.a が必要であることがわかります。 また libtapi をターゲットにして Makefile を実行しておりますが、 ここからMakefile2 の25526行目にある以下のような記述に到達します。

#=============================================================================
# Target rules for target projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir

# All Build rule for target.
projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/all: lib/Demangle/CMakeFiles/LLVMDemangle.dir/all
projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/all: lib/Support/CMakeFiles/LLVMSupport.dir/all
projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/all: lib/IR/CMakeFiles/LLVMCore.dir/all
projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/all: lib/BinaryFormat/CMakeFiles/LLVMBinaryFormat.dir/all
projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/all: lib/Bitcode/Reader/CMakeFiles/LLVMBitReader.dir/all
projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/all: lib/MC/CMakeFiles/LLVMMC.dir/all
projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/all: lib/MC/MCParser/CMakeFiles/LLVMMCParser.dir/all
projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/all: lib/Object/CMakeFiles/LLVMObject.dir/all
projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/all: projects/clang/lib/Basic/CMakeFiles/clangBasic.dir/all
projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/all: projects/libtapi/lib/Core/CMakeFiles/tapiCore.dir/all
projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/all: projects/libtapi/tools/libtapi/CMakeFiles/libtapi_exports.dir/all
	$(MAKE) -f projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/build.make projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/depend
	$(MAKE) -f projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/build.make projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/build
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --progress-dir=/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles --progress-num= "Built target libtapi"
.PHONY : projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/all

# Include target in all.
all: projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/all

.PHONY : all

# Build rule for subdir invocation for target.
projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/rule: cmake_check_build_system
	$(CMAKE_COMMAND) -E cmake_progress_start /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles 16
	$(MAKE) -f CMakeFiles/Makefile2 projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/all
	$(CMAKE_COMMAND) -E cmake_progress_start /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles 0
.PHONY : projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/rule

# Convenience name for target.
libtapi: projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/rule

.PHONY : libtapi

上記によると、ターゲットを「libtapi」として実行することにより、以下のように遷移します。

libtapi
→ projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/rule
→ projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/all

そして最初の方にターゲット「projects/libtapi/tools/libtapi/CMakeFiles/libtapi.dir/all」が必要とする ライブラリのターゲットが列挙されており、要するにこれら(とlibclangBasic.a)がlibtapiのビルドに必要なライブラリということです。

無意味の権化とも言うべきMakefile2において、ここは唯一意味のある情報と言えるかもしれません (まあ逆に言えばそのような情報が37000行もの無駄なコードの中に埋もれ、 簡単には発掘できない状態になっているとも言えます)。

尚、本来こういったものはcmakeの設定ファイルの方で簡潔にわかりやすく書かれて然るべきであろうかと思います。 実際、src/llvm/projects/libtapi/lib/Core/CMakeLists.txt(そもそもこれが置かれている位置自体が分かりやすくもないですが)に 以下のような記述があります。

set( LLVM_LINK_COMPONENTS
  Object
  BinaryFormat
  )

add_tapi_library(tapiCore
  Architecture.cpp
  ArchitectureSet.cpp
  ArchitectureSupport.cpp
  API.cpp
  APIPrinter.cpp
  APIVisitor.cpp
  AvailabilityInfo.cpp
  FakeSymbols.cpp
  FileManager.cpp
  FileSystem.cpp
  Framework.cpp
  ExtendedInterfaceFile.cpp
  HeaderFile.cpp
  InterfaceFile.cpp
  InterfaceFileBase.cpp
  InterfaceFileManager.cpp
  JSONFile.cpp
  MachODylibReader.cpp
  Path.cpp
  Platform.cpp
  ReexportFileWriter.cpp
  Registry.cpp
  Symbol.cpp
  TapiError.cpp
  TextStub_v1.cpp
  TextStub_v2.cpp
  TextStub_v3.cpp
  Utils.cpp
  XPI.cpp
  XPISet.cpp
  YAML.cpp
  YAMLReaderWriter.cpp

  LINK_LIBS
  clangBasic
  )

しかしこのファイルで明確に書かれているライブラリ名は、clangBasic のみですね。 かろうじて上の方にはObject、BinaryFormatといった文字列がありますが、 これが libLLVMObject.a、LLVMBinaryFormat.a のことを示しているのかどうかは微妙な感じですし、 その他のライブラリについては欠片すらありません。 また、ここからどう辿っていけばよいかという手がかり (まっとうなスクリプトならimportと書かれるような記述)がこのcmakeのスクリプトにはなく、 これならまだ追跡の手がかりの書かれているMakefileを見た方が速いのではないでしょうか?

どうでもいいですが、なぜこの CMakeLists.txt (cmakeのスクリプト言語と言うべきものなのでしょうか?)は拡張子がtxtなのでしょう? 通常、テキストエディタのハイライト機能は拡張子でそのファイルタイプを判断すると思います。 拡張子が txt だとテキストエディタで色が付かんザマスわよ

Makefile37000Makefile2 については(本当はまだツッコミどころが無限にありますが)もういいでしょう。

Close


build.make に関する死亡診断書
次は、実際にLLVMSupportのビルドを行うための Makefile(lib/Support/CMakeFiles/LLVMSupport.dir/build.make)についても確認しておきましょう。 この build.make ファイルは1749行です(この行数とて十分異常なのですが、直前に見たMakefile2が異常すぎて相対的に普通に感じるかもしれません)。

ちなみに我々が作った LLVMSupportのビルド用 Makefile(mkfalt_apple/LLVMSupport.mak)の行数は250行です。

まず build.make の最初の58行あたりまでを以下に示しましょう。

# CMAKE generated file: DO NOT EDIT!
# Generated by "Unix Makefiles" Generator, CMake Version 3.13

# Delete rule output on recipe failure.
.DELETE_ON_ERROR:


#=============================================================================
# Special targets provided by cmake.

# Disable implicit rules so canonical targets will work.
.SUFFIXES:


# Remove some rules from gmake that .SUFFIXES does not remove.
SUFFIXES =

.SUFFIXES: .hpux_make_needs_suffix_list


# Suppress display of executed commands.
$(VERBOSE).SILENT:


# A target that is always out of date.
cmake_force:

.PHONY : cmake_force

#=============================================================================
# Set environment variables for the build.

# The shell in which to execute make rules.
SHELL = /bin/sh

# The CMake executable.
CMAKE_COMMAND = /home/zenkaku/Downloads/cmake_dir/install_dir/bin/cmake

# The command to remove a file.
RM = /home/zenkaku/Downloads/cmake_dir/install_dir/bin/cmake -E remove -f

# Escaping for special characters.
EQUALS = =

# The top-level source directory on which CMake was run.
CMAKE_SOURCE_DIR = /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm

# The top-level build directory on which CMake was run.
CMAKE_BINARY_DIR = /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build

# Include any dependencies generated for this target.
include lib/Support/CMakeFiles/LLVMSupport.dir/depend.make

# Include the progress variables for this target.
include lib/Support/CMakeFiles/LLVMSupport.dir/progress.make

# Include the compile flags for this target's objects.
include lib/Support/CMakeFiles/LLVMSupport.dir/flags.make

各種変数などが定義されています。 ただしこれらのうち実際に使われているのはVERBOSEとCMAKE_COMMANDだけです。 それよりも重要なのが最後にincludeしている「lib/Support/CMakeFiles/LLVMSupport.dir/flags.make」です。 ここにコンパイルフラグとリンクフラグが指定されています。 一番見たい情報はこれです。 早速flags.makeの中身を見てみます。 以下の通りです。

# CMAKE generated file: DO NOT EDIT!
# Generated by "Unix Makefiles" Generator, CMake Version 3.13

# compile C with /home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang
# compile CXX with /home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++
C_FLAGS =  -fPIC -Werror=date-time -Wall -W -Wno-unused-parameter -Wwrite-strings -Wmissing-field-initializers -pedantic -Wno-long-long -Wcovered-switch-default -Wdelete-non-virtual-dtor -Wstring-conversion -ffunction-sections -fdata-sections -O3 -DNDEBUG  

C_DEFINES = -DGTEST_HAS_RTTI=0 -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS

C_INCLUDES = -I/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support -I/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support -I/usr/include/libxml2 -I/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/include -I/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/include 

CXX_FLAGS = -I /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/../src/llvm/projects/clang/include -I /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/projects/clang/include -fPIC -fvisibility-inlines-hidden -Werror=date-time -std=c++11 -Wall -W -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wmissing-field-initializers -pedantic -Wno-long-long -Wcovered-switch-default -Wnon-virtual-dtor -Wdelete-non-virtual-dtor -Wstring-conversion -ffunction-sections -fdata-sections -O3 -DNDEBUG  

CXX_DEFINES = -DGTEST_HAS_RTTI=0 -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS

CXX_INCLUDES = -I/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support -I/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support -I/usr/include/libxml2 -I/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/include -I/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/include 

非常に横に長くなっています。 というのもインクルードフラグ(「-I」オプション)で絶対パスが連発指定されている上、 改行コードもないからです。 上記に改行を入れて少し読みやすくしたものが以下になります。

# CMAKE generated file: DO NOT EDIT!
# Generated by "Unix Makefiles" Generator, CMake Version 3.13

# compile C with /home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang
# compile CXX with /home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++
C_FLAGS = \
	-fPIC -Werror=date-time \
	-Wall -W -Wno-unused-parameter -Wwrite-strings -Wmissing-field-initializers \
	-pedantic -Wno-long-long -Wcovered-switch-default -Wdelete-non-virtual-dtor -Wstring-conversion \
	-ffunction-sections -fdata-sections -O3 -DNDEBUG  

C_DEFINES = \
	-DGTEST_HAS_RTTI=0 \
	-D_GNU_SOURCE \
	-D__STDC_CONSTANT_MACROS \
	-D__STDC_FORMAT_MACROS \
	-D__STDC_LIMIT_MACROS \

C_INCLUDES = \
	-I/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support \
	-I/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support \
	-I/usr/include/libxml2 \
	-I/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/include \
	-I/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/include  \

CXX_FLAGS = \
	-I /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/../src/llvm/projects/clang/include \
	-I /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/projects/clang/include \
	-fPIC -fvisibility-inlines-hidden -Werror=date-time -std=c++11 \
	-Wall -W -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wmissing-field-initializers \
	-pedantic -Wno-long-long -Wcovered-switch-default -Wnon-virtual-dtor -Wdelete-non-virtual-dtor -Wstring-conversion \
	-ffunction-sections -fdata-sections -O3 -DNDEBUG  

CXX_DEFINES = \
	-DGTEST_HAS_RTTI=0 \
	-D_GNU_SOURCE \
	-D__STDC_CONSTANT_MACROS \
	-D__STDC_FORMAT_MACROS \
	-D__STDC_LIMIT_MACROS \

CXX_INCLUDES = \
	-I/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support \
	-I/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support \
	-I/usr/include/libxml2 \
	-I/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/include \
	-I/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/include \

このように揃えて書くと、以下の二つの絶対パスが冗長に指定されていることが一目瞭然になります。

/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm
/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build

ところでこれ、build.makeの最初で見た以下の変数と同じ値ではないでしょうか?

CMAKE_SOURCE_DIR = /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm

# The top-level build directory on which CMake was run.
CMAKE_BINARY_DIR = /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build

なぜこれらの変数 CMAKE_SOURCE_DIR と CMAKE_BINARY_DIR を (cmake自身が定義しておきながら)使わないのでしょうか?(こりゃただの飾りですかい?) こんな長ったらしい絶対パスを変数も使わずハードコーディングする意味など微塵もありません! 試しにこれを使って書き直してみた結果が以下です。

C_INCLUDES = \
	-I$(CMAKE_BINARY_DIR)/lib/Support \
	-I$(CMAKE_SOURCE_DIR)/lib/Support \
	-I/usr/include/libxml2 \
	-I$(CMAKE_BINARY_DIR)/include \
	-I$(CMAKE_SOURCE_DIR)/include  \

CXX_FLAGS = \
	-I $(CMAKE_SOURCE_DIR)/projects/clang/include \
	-I $(CMAKE_BINARY_DIR)/projects/clang/include \
	-fPIC -fvisibility-inlines-hidden -Werror=date-time -std=c++11 \
	-Wall -W -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wmissing-field-initializers \
	-pedantic -Wno-long-long -Wcovered-switch-default -Wnon-virtual-dtor -Wdelete-non-virtual-dtor -Wstring-conversion \
	-ffunction-sections -fdata-sections -O3 -DNDEBUG  

CXX_INCLUDES = \
	-I$(CMAKE_BINARY_DIR)/lib/Support \
	-I$(CMAKE_SOURCE_DIR)/lib/Support \
	-I/usr/include/libxml2 \
	-I$(CMAKE_BINARY_DIR)/include \
	-I$(CMAKE_SOURCE_DIR)/include \

恐ろしくすっきりしました。

勿論、これが自動生成されたMakefileであることは百も承知ですが、 変数を含めて生成するくらい簡単なことのはずです。 なぜ変数を含めずに生成しているのか、同じような生成ツールを作っている私からすれば甚だ疑問です。

こういうものはあまりにガチガチに大量の変数で固め過ぎるのも逆に問題ですし(GNU configureが吐くMakefileはその悪い例ですが)、 自動生成である以上、多少の冗長性が生じるのはやむをえない部分もあります。 しかしながら cmake が吐くMakefileについては多少の冗長性というレベルではありません。 あまりに度を超えています。 もしかして難読化させるためにワザとやっているのでしょうか?
これについて言いたいことはまだあります。

通常、apple-libtapiのビルドはカレントディレクトリが 「/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build」である状態で行われます。 ここにエントリーとなるMakefileがあるためです。 ということは、ここはわざわざ絶対パスで指定せずとも、通常は以下のように相対パスで書けばよいわけです。

CMAKE_SOURCE_DIR = src/llvm

# The top-level build directory on which CMake was run.
CMAKE_BINARY_DIR = .

恐ろしく短くなりました。

ただし他のディレクトリからも実行できるようにここを絶対パスで書きたいというのも一応理解できます (絶対パスではなく環境変数を使う方法もあるでしょう)。 しかしいずれにせよ、あちこちに直書するのではなく一旦変数を介在させるべきです。 あちこちに直書してしまっては(他のPCへ移動するなどして)いざこのパスを修正したいとなったとき、すべての箇所を冗長に修正する必要があります (それがたとえ自動生成するMakefileであっても無意味におかしな書き方で生成する必要はないはずです)。

さて、ここまででも既に嫌な予感がしますが、 build.makeの残りの部分も確認いたしましょう。 以下になります。

lib/Support/CMakeFiles/LLVMSupport.dir/AMDGPUMetadata.cpp.o: lib/Support/CMakeFiles/LLVMSupport.dir/flags.make
lib/Support/CMakeFiles/LLVMSupport.dir/AMDGPUMetadata.cpp.o: /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/AMDGPUMetadata.cpp
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --progress-dir=/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_1) "Building CXX object lib/Support/CMakeFiles/LLVMSupport.dir/AMDGPUMetadata.cpp.o"
	cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support && /home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++  $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS)  -fno-exceptions -fno-rtti -o CMakeFiles/LLVMSupport.dir/AMDGPUMetadata.cpp.o -c /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/AMDGPUMetadata.cpp

lib/Support/CMakeFiles/LLVMSupport.dir/AMDGPUMetadata.cpp.i: cmake_force
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green "Preprocessing CXX source to CMakeFiles/LLVMSupport.dir/AMDGPUMetadata.cpp.i"
	cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support && /home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS)  -fno-exceptions -fno-rtti -E /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/AMDGPUMetadata.cpp > CMakeFiles/LLVMSupport.dir/AMDGPUMetadata.cpp.i

lib/Support/CMakeFiles/LLVMSupport.dir/AMDGPUMetadata.cpp.s: cmake_force
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green "Compiling CXX source to assembly CMakeFiles/LLVMSupport.dir/AMDGPUMetadata.cpp.s"
	cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support && /home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS)  -fno-exceptions -fno-rtti -S /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/AMDGPUMetadata.cpp -o CMakeFiles/LLVMSupport.dir/AMDGPUMetadata.cpp.s

lib/Support/CMakeFiles/LLVMSupport.dir/APFloat.cpp.o: lib/Support/CMakeFiles/LLVMSupport.dir/flags.make
lib/Support/CMakeFiles/LLVMSupport.dir/APFloat.cpp.o: /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/APFloat.cpp
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --progress-dir=/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_2) "Building CXX object lib/Support/CMakeFiles/LLVMSupport.dir/APFloat.cpp.o"
	cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support && /home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++  $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS)  -fno-exceptions -fno-rtti -o CMakeFiles/LLVMSupport.dir/APFloat.cpp.o -c /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/APFloat.cpp

lib/Support/CMakeFiles/LLVMSupport.dir/APFloat.cpp.i: cmake_force
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green "Preprocessing CXX source to CMakeFiles/LLVMSupport.dir/APFloat.cpp.i"
	cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support && /home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS)  -fno-exceptions -fno-rtti -E /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/APFloat.cpp > CMakeFiles/LLVMSupport.dir/APFloat.cpp.i

lib/Support/CMakeFiles/LLVMSupport.dir/APFloat.cpp.s: cmake_force
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green "Compiling CXX source to assembly CMakeFiles/LLVMSupport.dir/APFloat.cpp.s"
	cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support && /home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS)  -fno-exceptions -fno-rtti -S /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/APFloat.cpp -o CMakeFiles/LLVMSupport.dir/APFloat.cpp.s

lib/Support/CMakeFiles/LLVMSupport.dir/APInt.cpp.o: lib/Support/CMakeFiles/LLVMSupport.dir/flags.make
lib/Support/CMakeFiles/LLVMSupport.dir/APInt.cpp.o: /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/APInt.cpp
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --progress-dir=/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_3) "Building CXX object lib/Support/CMakeFiles/LLVMSupport.dir/APInt.cpp.o"
	cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support && /home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++  $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS)  -fno-exceptions -fno-rtti -o CMakeFiles/LLVMSupport.dir/APInt.cpp.o -c /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/APInt.cpp

lib/Support/CMakeFiles/LLVMSupport.dir/APInt.cpp.i: cmake_force
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green "Preprocessing CXX source to CMakeFiles/LLVMSupport.dir/APInt.cpp.i"
	cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support && /home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS)  -fno-exceptions -fno-rtti -E /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/APInt.cpp > CMakeFiles/LLVMSupport.dir/APInt.cpp.i

lib/Support/CMakeFiles/LLVMSupport.dir/APInt.cpp.s: cmake_force
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green "Compiling CXX source to assembly CMakeFiles/LLVMSupport.dir/APInt.cpp.s"
	cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support && /home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS)  -fno-exceptions -fno-rtti -S /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/APInt.cpp -o CMakeFiles/LLVMSupport.dir/APInt.cpp.s

	…1400行以上もあるので途中略…

lib/Support/CMakeFiles/LLVMSupport.dir/Valgrind.cpp.o: lib/Support/CMakeFiles/LLVMSupport.dir/flags.make
lib/Support/CMakeFiles/LLVMSupport.dir/Valgrind.cpp.o: /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/Valgrind.cpp
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --progress-dir=/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_110) "Building CXX object lib/Support/CMakeFiles/LLVMSupport.dir/Valgrind.cpp.o"
	cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support && /home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++  $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS)  -fno-exceptions -fno-rtti -o CMakeFiles/LLVMSupport.dir/Valgrind.cpp.o -c /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/Valgrind.cpp

lib/Support/CMakeFiles/LLVMSupport.dir/Valgrind.cpp.i: cmake_force
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green "Preprocessing CXX source to CMakeFiles/LLVMSupport.dir/Valgrind.cpp.i"
	cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support && /home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS)  -fno-exceptions -fno-rtti -E /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/Valgrind.cpp > CMakeFiles/LLVMSupport.dir/Valgrind.cpp.i

lib/Support/CMakeFiles/LLVMSupport.dir/Valgrind.cpp.s: cmake_force
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green "Compiling CXX source to assembly CMakeFiles/LLVMSupport.dir/Valgrind.cpp.s"
	cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support && /home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS)  -fno-exceptions -fno-rtti -S /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/Valgrind.cpp -o CMakeFiles/LLVMSupport.dir/Valgrind.cpp.s

lib/Support/CMakeFiles/LLVMSupport.dir/Watchdog.cpp.o: lib/Support/CMakeFiles/LLVMSupport.dir/flags.make
lib/Support/CMakeFiles/LLVMSupport.dir/Watchdog.cpp.o: /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/Watchdog.cpp
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --progress-dir=/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_111) "Building CXX object lib/Support/CMakeFiles/LLVMSupport.dir/Watchdog.cpp.o"
	cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support && /home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++  $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS)  -fno-exceptions -fno-rtti -o CMakeFiles/LLVMSupport.dir/Watchdog.cpp.o -c /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/Watchdog.cpp

lib/Support/CMakeFiles/LLVMSupport.dir/Watchdog.cpp.i: cmake_force
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green "Preprocessing CXX source to CMakeFiles/LLVMSupport.dir/Watchdog.cpp.i"
	cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support && /home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS)  -fno-exceptions -fno-rtti -E /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/Watchdog.cpp > CMakeFiles/LLVMSupport.dir/Watchdog.cpp.i

lib/Support/CMakeFiles/LLVMSupport.dir/Watchdog.cpp.s: cmake_force
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green "Compiling CXX source to assembly CMakeFiles/LLVMSupport.dir/Watchdog.cpp.s"
	cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support && /home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS)  -fno-exceptions -fno-rtti -S /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/Watchdog.cpp -o CMakeFiles/LLVMSupport.dir/Watchdog.cpp.s

...途中省略…

lib/libLLVMSupport.a: lib/Support/CMakeFiles/LLVMSupport.dir/link.txt
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --bold --progress-dir=/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_112) "Linking CXX static library ../libLLVMSupport.a"
	cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support && $(CMAKE_COMMAND) -P CMakeFiles/LLVMSupport.dir/cmake_clean_target.cmake
	cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support && $(CMAKE_COMMAND) -E cmake_link_script CMakeFiles/LLVMSupport.dir/link.txt --verbose=$(VERBOSE)

# Rule to build all files generated by this target.
lib/Support/CMakeFiles/LLVMSupport.dir/build: lib/libLLVMSupport.a

.PHONY : lib/Support/CMakeFiles/LLVMSupport.dir/build

... 以下省略 ...

やはりか! 嫌な予感が的中で絶対パスだらけです。 また変数 CMAKE_SOURCE_DIR、CMAKE_BINARY_DIR についても当然のように使われておりません。 「あちこちに直書」というより「1749行ものファイル全体に悉く直書」してしまっているのです。

しかし真におぞましい事実がこれから判明します。 上記は各オブジェクトファイルをビルドするためのルールの大群なのですが、 build.make ファイル(全1749行)のうち、この大群が書かれた部分が1440行にもなっています。 なぜわざわざ全オブジェクトファイルについて、冗長にも個別にルールを書いているのでしょうか?

あるオブジェクトファイルについては特別なフラグ指定が必要だとか、そういった理由で個別に書くのであれば理解できます。 しかし今回のケースでは全てのオブジェクトファイルについてその指定は共通です。 このような場合、全体を抽象化したサフィックスルールを一つ書けば、全てのオブジェクトファイルに対応できるはずです。 なぜそれを使わないのでしょうか?

今回のケースの場合、GNU Makefileではパターンルールと呼ばれるものを使うことになります。 ただし「パターンルール」という言葉はGNU Makefile独自の機能と用語であることと、 サフィックスルールもパターンルールも(パターンマッチの範囲を除けば)本質的には同様の仕事(ビルドルールの抽象化)をすることを鑑み、 この記事ではとりあえず「サフィックスルール」という用語を総称的に使うことにします。

尚、今回はGNU Makefileの話ですので直接関係ありませんが、 GNU 以外の Makefile においては、サフィックスルールで 入力ディレクトリと出力ディレクトリを明示的に指定できないものもあります。 例えば VC 付属の nmake の Makefile ではそのような指定が可能ですが、 一方で BSD の Makefile ではそのような指定ができません。 ただし BSD の Makefile でも for ループを使えば同等のことは可能です。

だからと言って手放しに GNU Makefileのパターンルールを賞賛できるかと言われると、若干の不満点もあります。 GNU Makefile のパターンルールでは指定したディレクトリの配下のディレクトリまで再帰的に辿り非常に強力ですが、 これは個人的には少しやり過ぎな気もします。 このような挙動だとサブディレクトリ毎にオプションを変えたいような場合、逆にややこしいことになるからです。 VCの nmake のように、各ディレクトリ毎にサフィックスルールを指定できるくらいの仕様がちょうどよいのではないかと思います。 それにより多くのサブディレクトリがあるプロジェクトでは記述に「多少の冗長性」が発生するかもしれませんが、 ディレクトリ単位であれば許容できる冗長性です。

例として AMDGPUMetadata.cpp.o を生成するルールを一旦見てみます。 この部分だけ抜粋すると以下のようになっています(ただし見やすくするため改行を入れています)。

lib/Support/CMakeFiles/LLVMSupport.dir/AMDGPUMetadata.cpp.o: \
	/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/AMDGPUMetadata.cpp
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green \
		--progress-dir=/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles \
		--progress-num=$(CMAKE_PROGRESS_1) "Building CXX object lib/Support/CMakeFiles/LLVMSupport.dir/AMDGPUMetadata.cpp.o"
	cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support && \
		/home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++ \
		$(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS)  -fno-exceptions -fno-rtti \
		-o CMakeFiles/LLVMSupport.dir/AMDGPUMetadata.cpp.o \
		-c /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/AMDGPUMetadata.cpp

まず最初の「lib/Support/CMakeFiles/LLVMSupport.dir/AMDGPUMetadata.cpp.o:」に注目してください。 このように相対パスで書いてあるということは、このMakefileは やはりカレントディレクトリが「/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build」 であることを前提としています(これは変数で書けばCMAKE_BINARY_DIRです)。 さもなければこのファイル(ターゲット)の存在を認識できないからです。

一方、llvm_clangの実行により出力されるオブジェクトファイル「-o CMakeFiles/LLVMSupport.dir/AMDGPUMetadata.cpp.o」の部分 (最後から二番目の行あたり)に注目してください。 これも相対パスで書かれていますが、奇妙なことにターゲットの相対パスと少し異なっています。 ここが同じでないため、特殊変数「$@」が使えない状況になっています。 しかしなぜ異なっているのでしょうか?

中ほどに「cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support」 という謎の cd コマンドが実行されていますが、犯人はコヤツです! こんなものを意味も無く実行しているために、ターゲットのパスと「-o」で指定されたパスが異なってしまっているのです。 この cd コマンドは削除し、これらのパスを同じにしなければなりません。 以下のようになるでしょう。

lib/Support/CMakeFiles/LLVMSupport.dir/AMDGPUMetadata.cpp.o: \
	/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/AMDGPUMetadata.cpp
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green \
		--progress-dir=/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles \
		--progress-num=$(CMAKE_PROGRESS_1) "Building CXX object lib/Support/CMakeFiles/LLVMSupport.dir/AMDGPUMetadata.cpp.o"
	/home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++ \
		$(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS)  -fno-exceptions -fno-rtti \
		-o lib/Support/CMakeFiles/LLVMSupport.dir/AMDGPUMetadata.cpp.o \
		-c /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/AMDGPUMetadata.cpp

この形になれば「$@」が使えます。 ついでに「$<」も使っておきましょう。 以下のように書けます。

lib/Support/CMakeFiles/LLVMSupport.dir/AMDGPUMetadata.cpp.o: \
	/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/AMDGPUMetadata.cpp
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green \
		--progress-dir=/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles \
		--progress-num=$(CMAKE_PROGRESS_1) "Building CXX object $@"
	/home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++ \
		$(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS)  -fno-exceptions -fno-rtti \
		-o $@ \
		-c $<

ここまでくればサフィックスルールに書き換えることは容易でしょう。 元の1440行もの大群は、以下の9行だけになります(実に1/160という凄まじい圧縮率です)。

lib/Support/CMakeFiles/LLVMSupport.dir/%.cpp.o: \
	/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/src/llvm/lib/Support/%.cpp
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green \
		--progress-dir=/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles \
		--progress-num=$(CMAKE_PROGRESS_1) "Building CXX object $@"
	/home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++ \
		$(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS)  -fno-exceptions -fno-rtti \
		-o $@ \
		-c $<

さらにCMAKE_SOURCE_DIR、CMAKE_BINARY_DIR を使って書けば以下になります。

lib/Support/CMakeFiles/LLVMSupport.dir/%.cpp.o: $(CMAKE_SOURCE_DIR)/lib/Support/%.cpp
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green \
		--progress-dir=$(CMAKE_BINARY_DIR)/CMakeFiles \
		--progress-num=$(CMAKE_PROGRESS_1) "Building CXX object $@"
	/home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++ \
		$(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS)  -fno-exceptions -fno-rtti \
		-o $@ \
		-c $<

また、はっきり言って cmake のプログレス表示など要りませんので、その部分をカットすれば以下のようになります。

lib/Support/CMakeFiles/LLVMSupport.dir/%.cpp.o: $(CMAKE_SOURCE_DIR)/lib/Support/%.cpp
	/home/zenkaku/Downloads/llvm_dir/v3.9/install_dir/bin/clang++ \
		$(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS)  -fno-exceptions -fno-rtti -o $@ -c $<

なんと限界まで絞り込めばたったの3行で済む話でした。 それを実に1440行もの大群で書いていたのです。 cmake の開発陣は一体何を考えているのでしょうか? 仮にもMakefile生成ツールの開発者が、さすがにサフィックスルール(今回の場合パターンルールですが)のことを知らないはずがありません。 これもまた難読化させるためにワザとやっているのでしょうか?

最後に「lib/libLLVMSupport.a」をビルドするルールについても見ておきましょう。 これも改行を入れて少し見やすくしたものを以下に示します。

lib/libLLVMSupport.a: lib/Support/CMakeFiles/LLVMSupport.dir/link.txt
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --bold \
		--progress-dir=/home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/CMakeFiles \
		--progress-num=$(CMAKE_PROGRESS_112) "Linking CXX static library ../libLLVMSupport.a"
	cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support && \
		$(CMAKE_COMMAND) -P CMakeFiles/LLVMSupport.dir/cmake_clean_target.cmake
	cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support && \
		$(CMAKE_COMMAND) -E cmake_link_script CMakeFiles/LLVMSupport.dir/link.txt --verbose=$(VERBOSE)

arコマンドやオブジェクトファイルの指定が見当たりませんが、 それについては「lib/Support/CMakeFiles/LLVMSupport.dir/link.txt」に書かれてあります。 link.txtの中身は以下のようになっています(見やすくするために改行を入れて載せています)。

/usr/bin/ar qc ../libLLVMSupport.a  \
	CMakeFiles/LLVMSupport.dir/AMDGPUMetadata.cpp.o \
	...途中略(オブジェクトファイルの大群)... \
	CMakeFiles/LLVMSupport.dir/Watchdog.cpp.o \

/usr/bin/ranlib ../libLLVMSupport.a

要するに実際に実行すべきコマンドが普通に書かれているだけのシェルスクリプトです。 最後の CMAKE_COMMAND が、単にそこに書かれてあるコマンドをそのまま実行しているようです (これもよく意味がわかりませんが、それならわざわざ CMAKE_COMMAND などを呼び出さずとも、 「sh lib/Support/CMakeFiles/LLVMSupport.dir/link.txt」で済むことではあります)。

また「lib/libLLVMSupport.a」をビルドするルールの方の中ほどの位置に 「cd /home/zenkaku/Downloads/osxcross-v1.1/osxcross-osxcross-1.1/build/apple-libtapi/build/lib/Support」が 二回実行されております(移動先は全く同じです)。 あからさまに異常でしょう。 もうウンザリしますね。何のためにここで二回の cd (移動先は全く同じ)をやっているのでしょうか? この CMAKE_COMMAND の実行によってカレントディレクトリが変わることがあるとでもいうのでしょうか?

というわけで cmake が吐き出したMakefileはどいつもこいつも息をしておりません(即死です)。 以上、現場からZenkakuがお伝えしました。

Close


Close

またもう一つの問題点は、osxcross/build.sh を再実行した場合、 それまでに作ったはずの xar、apple-libtapi、cctools-portが問答無用で消去され、 再ダウンロード、ゼロからの再ビルドが行われてしまうことです。

(cmakeとは違って)osxcross/build.sh 自体はほぼ無駄がなくよく出来ておりますし、 我々もこれに書かれてある情報を基に、クロスコンパイル用のMakefileを作っています。 しかしエラーを解析するという観点からはこのスクリプトはいささか使い辛いものになります。

osxcross/build.sh を実行すると、 get_sources というシェルスクリプトの関数が実行されます(この関数の定義は osxcross/tools/tools.sh にあります)。 この関数内ではさらに git_clone_repository という関数が呼び出されますが、その定義が以下になります。

function git_clone_repository
{
  local url=$1
  local branch=$2
  local project_name=$3

  if [ -n "$TP_OSXCROSS_DEV" ]; then
    # copy files from local working directory
    rm -rf $project_name
    cp -r $TP_OSXCROSS_DEV/$project_name .
    if [ -e ${project_name}/.git ]; then
      pushd $project_name &>/dev/null
      git clean -fdx &>/dev/null
      popd &>/dev/null
    fi
    f_res=1
    return
  fi

  if [ ! -d $project_name ]; then
    git clone $url $args $project_name --depth 1
  fi

  pushd $project_name &>/dev/null

  git reset --hard &>/dev/null
  git clean -fdx &>/dev/null

  if git show-ref refs/heads/$branch &>/dev/null; then
    git fetch origin $branch
  else
    git fetch origin $branch:$branch --depth 1
  fi
  
  git checkout $branch
  git pull origin $branch

  local new_hash=$(git rev-parse HEAD)
  local old_hash=""
  local hash_file="$BUILD_DIR/.${project_name}_git_hash"

  if [ -f $hash_file ]; then
    old_hash=$(cat $hash_file)
  fi

  echo -n $new_hash > $hash_file

  if [ "$old_hash" != "$new_hash" ]; then
    f_res=1
  else
    f_res=0
  fi

  popd &>/dev/null
}

上記の中ほどで git clean コマンドを実行している箇所がありますが、これはちょっと問題です。 いえ、初めて実行する場合ならよいのです。 しかし再実行した場合、この git clean コマンドにより、せっかくダウンロードしたxar、apple-libtapi、cctools-portが消去され、 続く git 系のコマンドより、改めてこれらのダウンロードが行われる形になっています。

さすがにbuildし直しの度に毎回ダウンロードまでやり直されてはかないません(大したサイズでないプロジェクトならよいですが、 今回の場合、特にlibtapiが巨大なものになります)。 回避策ですが、要は指定されたプロジェクトのディレクトリ(ソースコード内では$project_nameという変数にその名前が格納されています)が 存在しないならダウンロードを行えばよいですし、 存在するなら f_res に 0 をセットした上で何もせずにこの関数を終了すべきです。 そこで tools.sh の298行目から300行目にある部分を、例えば以下のように修正すればよいでしょう。


  if [ ! -d $project_name ]; then
    git clone $url $args $project_name --depth 1
  else
    # this project dir already exists.
    f_res=0
    return
  fi


上記は元の記述に else と f_res=0 と return を追加しただけです。

しかしこのようにして再ダウンロードを抑制したとしても ゼロからの再ビルドについては依然として行われてしまいます。 その理由と回避策については以下をご覧下さい。

xarがゼロから再ビルドされる理由と回避策
xar についてこのような挙動になる理由を解説しましょう。

osxcross/build.sh 内で build_xar という関数が呼び出されます(この関数の定義は osxcross/tools/tools.sh にあります)。 中身は以下のようになっており、最初に get_sources 関数が呼び出され、 そこで f_res の値が 1 にセットされた場合、さらに configure が問答無用で呼び出されます。

function build_xar()
{
  pushd $BUILD_DIR &>/dev/null

  get_sources https://github.com/tpoechtrager/xar.git master

  if [ $f_res -eq 1 ]; then
    pushd $CURRENT_BUILD_PROJECT_NAME/xar &>/dev/null
    CFLAGS+=" -w" \
      ./configure --prefix=$TARGET_DIR
    $MAKE -j$JOBS
    $MAKE install -j$JOBS
    popd &>/dev/null
    build_success
  fi

  popd &>/dev/null
}

configure の実行により、xar.h という名前のファイルが自動的に生成されます。 あるいは既にこれが存在する場合はそのタイムスタンプが最新の時刻に更新されます。 さらに運悪く、この xar.h は xar に含まれるC言語ソースのほとんどが共通で参照するヘッダとなっているため、 続くmake実行時において、ほぼすべてのC言語のソースにおいて再コンパイルが行われてしまうわけです。

回避策ですが、さしあたっては osxcross/tools.sh の build_xar 関数の定義において、 configure 実行部分を if 文ではさみこみ、 Makefileがまだ存在しない場合のみconfigureの実行をするようにします。 以下のようになります。

function build_xar()
{
  pushd $BUILD_DIR &>/dev/null

  get_sources https://github.com/tpoechtrager/xar.git master

  if [ $f_res -eq 1 ]; then
    pushd $CURRENT_BUILD_PROJECT_NAME/xar &>/dev/null

	# if Makefile does not exists
    if test ! -e Makefile ; then
      CFLAGS+=" -w" \
        ./configure --prefix=$TARGET_DIR
    fi

    $MAKE -j$JOBS
    $MAKE install -j$JOBS
    popd &>/dev/null
    build_success
  fi

  popd &>/dev/null
}

Close


apple-libtapiがゼロから再ビルドされる理由と回避策
apple-libtapi についてこのような挙動になる理由を解説しましょう。

osxcross/build.sh 内で、osxcross/build/apple-libtapi/build.sh という関数が呼び出されますが、 これが言ってしまえば apple-libtapiのビルドのエントリーです。

正確には、一旦CURRENT_BUILD_PROJECT_NAMEという変数にapple-libtapiがセットされ、 そのディレクトリに移動するコマンド(pushd)が実行された後に、「./build.sh」が実行されます。

このosxcross/build/apple-libtapi/build.shの25行目あたりに 「rm -rf build」を実行している箇所があり、 これがディレクトリ osxcross/build/apple-libtapi/build を完全消去してしまいます。 そのため、それまでに作ったlibtapi関係のオブジェクトファイルやライブラリも一旦すべて消去され、 最初からフルビルドを行うことになるわけです。

例えば apple-libtapi のビルドが成功し cctools-port のビルドが失敗している場合ならば、 cctools-portのビルドだけを手直ししてやり直せばよいわけですが、 既に成功している apple-libtapi のビルドまで再びいっしょに行われてしまいます。 (それでもapple-libtapiのビルドが一瞬で終わるなら気になりませんが、 残念ながらこれは巨大なためビルドにも著しく時間がかかります(環境にもよるでしょうが筆者の環境では25分くらい掛かります))。 またこのフルビルドにおいては途中でcmakeが実行されますが、これがまた1、2分ほどかかり地味に邪魔です。

回避策ですが、osxcross/build/apple-libtapi/build.shの25行目の 「rm -rf build」を、 以下のようにコメントアウトしておくとよいでしょう。

# rm -rf build.sh

Close


このように、osxcross/build.sh を何の修正もなくそのまま実行した場合、 いざエラーが起きた場合にどこでどう起きたのか分析し難い上、気軽に部分的な再実行/再試行もできないという 問題があるわけです (osxcross/build.shの実行が絶対に一回で成功するという自信があるならよいですが、 現実には途中で何らかの予期せぬエラーが起きることが往々にしてあります)。

これに対して我々のとった方法は build.sh が中で行われている処理を一旦バラバラに分解し、 個別に攻略するというものです。 例えば xar、apple-libtapi、cctools-port を予め手動でダウンロードしておけば、 少なくともダウンロードに関するエラーがその先発生する可能性を排除できます。

その上で、それぞれを一旦分かりやすい位置に置き、個別に対処するのが最善の策ではないかと思います。 一度に考えなければならない範囲は局所化すべきです。 特に apple-libtapi と cctools-port は強敵です。 何も強敵二人を一度に同時に(一つのスクリプトで)相手する必要はありません。 まずはこれらを各個撃破しましょう、といったスタンスです。

osxcross がオリジナルで用意している方法(実際の手順)


手順といっても、必要なものさえ揃えば後は osxcross/build.sh を実行するだけですので、 それを揃えるまでの準備が重要となります。

MacOSX SDKの選定とダウンロードについては終わっているものとします(これがまだな方は既に述べた「MacOSX SDKの入手」の項を参照してください)。 またllvm_clang についてもインストール済みであるとします(インストールがまだな方は既に述べた「llvm_clangの入手」の項を参照してください)。 念のためここでも繰り返しておきますが、使用する llvm_clang のバージョンにはくれぐれも注意してください。

osxcrossのビルドに忠実に従う場合、内部で gmake(GNU make)に加え、cmake とやらを使うため、 残念ながらこの cmake とやらをインストールする必要があります。

一応、お使いのLinuxで既にこれがインストールされているかを確認してください。 ターミナルから次のように実行します。

cmake --version

おそらく「Command not found.」と表示される確率が一番高いとは思いますが、 この場合は cmake がインストールされていませんので、これに対処する必要があります。

運よく(?)「cmake version 3.13.5」などと出ればもうcmakeはお使いのシステムにインストールされています。 何もする必要はないです。

パッケージ管理システムで一時的にインストールして、 ld64までのビルドが終わったら即刻アンインストールでよいですが、 その場合 root 権限が必要となるでしょう。 たとえ一時的であれcmakeとやらで root 権限を発動させるのが嫌な方は こいつをソースからビルドしましょう(cmakeだけがインストールされたディレクトリを用意し、 今回のミッションの一部始終が終わったらこのディレクトリごと跡形もなく消します)。

cmakeのソースからのビルド
以下のcmake公式サイトからソースコードをダウンロードして(GNU makeで)makeします。


バージョンにそこまで拘る必要はないですが、この記事ではVer3.15.5を使います。

ここでVer3.15.5を選んだのは大した理由ではなく、単にこれがWindows XPをサポートするcmakeの最終バージョンだからです (これより後のバージョンのcmakeはWindows XPで動作しなくなります)。 勿論、今使っているのはLinuxであるため、このことは今は直接関係ありません。

注意点として、ソースコードとしてcmake-3.13.5.zipを使ってはいけません。 これではなく必ず cmake-3.13.5.tar.gz の方を使うようにしてください。 いや cmake-3.13.5.zip の方も同じソースコード(のハズ)ではないかと思われるかもしれませんが、 こちらは改行コードが原因で腐っています。 cmake-3.13.5.zip と cmake-3.13.5.tar.gz の両方に configure や GNU Makefile が存在するのですが、 前者でそれを実行した場合はコンパイルエラーが発生し、後者でそれを実行した場合は問題ないということです。 何なんでしょうか? いえ、こんなしょうもないツールのしょうもない不備で嵌り、時間と体力を消耗している場合ではありません。 先を急ぎます。

時間と体力を消耗する
cmake-3.13.5.zip ではテキストファイル全般にバカげたDOS改行変換を(おそらくツールを使って)何も考えずに施してあります。 この影響でこれをLinuxでコンパイルすると、bootstrapを実行中に生成される Bootstrap.cmk/cmVersionConfig.h において コンパイルエラーが発生しそこで止まります(Makefileを生成するためのツールが自分自身のMakefileを生成中に止まるという なんとも皮肉な話ですが)。

bootstrap では以下のような処理を行い、最終的に Bootstrap.cmk/cmVersionConfig.h を生成しているものと思われます。

# Write CMake version
cmake_report cmVersionConfig.h${_tmp} "#define CMake_VERSION_MAJOR ${cmake_version_major}"
cmake_report cmVersionConfig.h${_tmp} "#define CMake_VERSION_MINOR ${cmake_version_minor}"
cmake_report cmVersionConfig.h${_tmp} "#define CMake_VERSION_PATCH ${cmake_version_patch}"
cmake_report cmVersionConfig.h${_tmp} "#define CMake_VERSION \"${cmake_version}\""

上記の変数 cmake_version の値の生成のされ方が問題です。 これは同ファイルで以下のようにして作られております。

# Load version information.
cmake_version_major="`cmake_version_component MAJOR`"
cmake_version_minor="`cmake_version_component MINOR`"
cmake_version_patch="`cmake_version_component PATCH`"
cmake_version="${cmake_version_major}.${cmake_version_minor}.${cmake_version_patch}"

上記の変数 cmake_version_major は、cmake_version_component 関数に MAJOR という文字列引数を与え、 その結果標準出力される内容を受け取るといった形で初期化されています。 この cmake_version_component 関数の定義は同ファイルの17行目ほどにあり、以下の通りです。

# Version number extraction function.
cmake_version_component()
{
  sed -n "
/^set(CMake_VERSION_${1}/ {s/set(CMake_VERSION_${1} *\([0-9]*\))/\1/;p;}
" "${cmake_source_dir}/Source/CMakeVersion.cmake"
}

ここで、Source/CMakeVersion.cmake ファイルを入力として参照しております。 cmake-3.13.5.tar.gz では、このファイルはUNIX改行(0x0A)ですが、 cmake-3.13.5.zip では、このファイルがDOS改行(0x0D 0x0A)となっています。

この sed コマンドは少し難解ですが、まず「/^set(CMake_VERSION_${1}/」の部分により、 Source/CMakeVersion.cmake ファイルの中の「set(CMake_VERSION_MAJOR」で始まる行だけに入力を絞りこんでいます。 さらに「{s/set(CMake_VERSION_${1} *\([0-9]*\))/\1/;p;}」の部分により、 「set(CMake_VERSION_MAJOR 3)」となっている行を、単に「3」という行に変換し、標準出力(「p;」の部分)しています。 単に「3」という行と述べましたが、これにはまだ改行コードが付属していることに留意してください (要するにこの改行コードを完全に除去するのが本来正しい処置ですが、それを怠っているということです)。

これにより、cmake_version_component関数の出力結果は、 cmake-3.13.5.tar.gz では、「30x0A」(3 とUNIX改行)となりますが、 cmake-3.13.5.zip では、「30x0D0x0A」(3 とDOS改行)となります。

従って「cmake_version_major="`cmake_version_component MAJOR`"」を実行すると、0x0Aだけが除去され、 cmake-3.13.5.tar.gz では、cmake_version_major に「3」が代入され、 cmake-3.13.5.zip では、cmake_version_major に「30x0D」が代入されることになります。

よって最終的に cmake-3.13.5.tar.gz では、cmake_version の値は「3.13.5」となりますが、 cmake-3.13.5.zip では、cmake_version の値は「30x0D.130x0D.50x0D」などとなってしまいます。 その結果、cmake-3.13.5.zip の方では「#define CMake_VERSION "30x0D.130x0D.50x0D"」などという奇妙なコードが出来上がります。

ここで「0x0D」の部分は生のCR(Carriage Return)であることに留意してください。 g++ は文字列の途中に出現した「生のCR」を「生の改行」とみなしますが、 これは文字列リテラルがダブルクォートで終端する前に途中で「生の改行」が現れたことを意味します。 C言語/C++におけるダブルクォートによる文字列リテラルでは「生の改行」を含めることはできません。 これがコンパイルエラーとなる原因です(デバッグ完了)。

というか何で私がこんなことまでデバッグせにゃならんのだ…?

今時、改行コードが原因でコンパイルエラーになるC言語/C++ベースのプロジェクトのソースコードなどほとんどお目にかかれません。 超レアキャラです。

しかし仮にもテキストデータ(Makefile)の自動生成が本職であるようなツールが、 なぜテキスト生成において最も初歩的な改行コードの処理の部分でミスをしているのか? しかも一回でもビルドすればコンパイルエラーが出るわけで、このような不備があることは開発陣にもすぐにでもわかるはずですが、 それともわかっていて敢えて配布しているのでしょうか?

そもそも何で同じソースコード(ただし改行コードだけ違う)が今時冗長にも二つ用意されているのか私には理解できませんが、 何せ冗長の極みとでも言うべき 37000行ものMakefile を作るようなツールの開発陣ですので、 我々の見えない何かが見えている可能性もあります。

Close


cmakeのソースコードをダウンロードしましたら、 一旦 $HOME/Downloads/cmake_dir ディレクトリを作り、 cmake-3.13.5.tar.gz をそこへ隔離移動しておきます。 次のように実行してください。

cd $HOME/Downloads
mkdir -p cmake_dir
mv cmake-3.13.5.tar.gz cmake_dir/

$HOME/Downloads/cmake_dir ディレクトリへ入り、cmake-3.13.5.tar.gz を展開します。 次のように実行してください。

cd cmake_dir
tar xvzf cmake-3.13.5.tar.gz

cmake-3.13.5 ディレクトリへ入り、コンパイルを行います。 次のように実行してください(bootstrapの替わりにconfigureでも構いません。 中身を見ればわかりますがこのconfigureはbootstrapの単なるラッパーです)。

./bootstrap --prefix=../install_dir
make
make install

cmakeを作るためにmakeを実行するという、なんだか鶏と卵な気分になりますが、 よく考えたらコイツはmakeの代替でも同列でもなんでもなく、それに乗っかった単なるMakefile生成ツールなので、 卵の方ですかね…(中身半分腐ってますが)。

make installまで滞りなく終わったならば、$HOME/Downloads/cmake_dir/install_dir ディレクトリに cmake関係のファイル一式がインストールされているはずです。

念のため、$HOME/Downloads/cmake_dir/install_dir/bin/cmake が無事に起動することを確認してから、 PATHを通しましょう。 次のように実行してください。

export PATH=$HOME/Downloads/cmake_dir/install_dir:$PATH

単に「cmake」と入力してコマンドが起動するのを確認してください (cmakeの場合、多分LD_LIBRARY_PATHを設定する必要はないと思います)。

こんなものに環境変数PATHを汚染させたくありませんが、 osxcross では単に cmake と打って実行できる状況を前提としているのでやむを得ません。

尚、ターミナルを終了すれば PATH の値は自動的に元に戻ります。 だからといって $HOME/.bashrc に上記を書いておくのもそれはそれで環境汚染です。 必要なら上記の内容のシェルスクリプト setup_cmake.sh を作っておき、「sources ./setup_cmake.sh」を実行するのが 一番クリーンで環境に易しい方法でしょう。

尚、ディスクスペースの無駄ですので、cmake-3.13.5.tar.gz と その展開ディレクトリcmake-3.13.5 については直ちに削除しましょう。

cmake-3.13.5.tar.gz の方は大したサイズではありません(8MB)が、 展開ディレクトリ cmake-3.13.5 の方はビルド完了時点でなんと412MBにも膨れ上がります。 なぜこんなにも膨れ上がるのかもはや考えたくもありませんが、 いずれにせよこんなしょうもないモノで、ディスクスペースを400MB以上も消耗した状態を維持すべきではありません。 ビルドが終わったら即刻削除です。

cmake_dir/install_dir ディレクトリはトータルで85MB程度になると思います。 こちらは osxcrossのビルドが終わるまでは残しておく必要がありますが、 osxcrossのビルドが終わったら cmake_dir ごと削除しましょう。

また何かの機会で cmake を使うことがあるかもしれないと思ってとっておくとしても、 このインストールディレクトリだけを圧縮して保存しておけばよいです。
Close


これで必要なSDKとツールは揃いました。 後はosxcrossの本体についてどのバージョンを使うかです。 尚、xar、apple-libtapi、cctools-portについてはosxcross本体が自動的にダウンロードするため、 手動で用意する必要がありません(しかしだからこそosxcross本体の選定が非常に重要ですし、 「注意点」で述べた通り、細部が隠蔽されていることによりいざエラーが発生した場合の分析や対処はそれだけ難しくなります)。

改めてosxcrossの公式サイトhttps://github.com/tpoechtrager/osxcrossを開きましょう。 この記事を執筆している時点において、入手できる osxcross のバージョンは基本的に v1.5 と v1.1 の二つのようです。

  • osxcross v1.5 をダウンロードする場合
  • 画面上方の真ん中よりやや右にある「Code」と書かれた緑色のボタンを押すと、 「Download ZIP」と表示されたプルダウンメニューが出るはずです。 このメニューから「Download ZIP」を選びます。 osxcross-master.zip という名前のファイルをダウンロードすることができますが、 これが osxcross v1.5 の本体です。

  • osxcross v1.1 をダウンロードする場合
  • osxcross v1.1 の場合、画面左上にある「master」と書かれたボタンを押すと、 「Switch branches/tags」というタイトルのプルダウンメニューが出るはずです。

    このメニューから「osxcross-1.1」を選ぶと 先ほど「master」と書かれていたボタンの名前が「osxcross-1.1」という名前に変わります (そもそもページ自体が新しいものに遷移し、URLもこの時点で最後が「osxcross-1.1」に変化しているはずです)。 ここから先は osxcross v1.5 と同様に、画面上方の真ん中よりやや右にある「Code」と書かれた緑色のボタンからファイルをダウンロードします。 「Download ZIP」と表示されたプルダウンメニューが表示されますから、 このメニューから「Download ZIP」を選びます。 osxcross-osxcross-1.1.zip という名前のファイルをダウンロードすることができますが、 これが osxcross v1.1 の本体です。
一般的なツールとは異なり、これは様々なものが絡み合うかなり複雑なシステムです。 このような場合、単純にバージョンが新しければよいというものでもありません。

我々が確認した限りでは、v1.1 を使った場合は幸い一度のエラーもなく終わらせることができました。 一方、v1.5 を使った場合は少しだけエラーが発生し、それを手直しする必要がありました。

重要なのは全体的なバージョンの組み合わせが適切であるかどうかという点です。 組み合わせによっては、バージョンが新しいものの方が難易度が上がる可能性もあることに注意してください(勿論その逆のケースもあり得ます)。
選択肢はこの二つだけとはいえ、CHANGELOG からこれまでのバージョンの経緯を把握しておくことは今回の場合重要です。 この経緯の中に発生したエラーを解決するためのヒントが書かれてある場合もあります。

v1.5のCHANGELOGでは以下のように記述されています。

/****************************** v1.5 *********************************/

Changed:
 * Updated cctools to 986 and ld64 to 711
 * Update TAPI library to 1300.6.5

即ち、osxcross v1.5 では、apple-libtapi-1300.6.5 を、 cctools-port-986-ld64-711 を使うことになります。 xar についてはそのバージョンを気にする必要はありません。

MacOS SDK については 11.1(darwin20.2)の方を使うとよいでしょう (10.13(darwin17)の方を使うこともできますが、 それならばわざわざ osxcross-v1.5 を使う必要がなく osxcross-v1.1 で十分です。 osxcross-v1.1 のビルドの方が簡単に終わる確率が高いです)
v1.1のCHANGELOGでは以下のように記述されています。

/****************************** v1.1 *********************************/

Added:
 * ... back support for OpenBSD

Changed:
 * Updated cctools to 927.0.2 and ld64 to 450.3
 * TARGET_DIR can now be configured via env
   (TARGET_DIR=<path> ./build.sh)
 * Various other improvements and fixes

即ち、osxcross v1.1 では、cctools-port-927.0.2-ld64-450.3 を使うことになります。 apple-libtapi については記載がありませんが、 v1.1 に同梱されている build.sh の記述から、apple-libtapi-1000.10.8を使うものと判断します。 xar についてはそのバージョンを気にする必要はありません。

MacOS SDK については 10.13(darwin17)の方を使うことになります。
参考までにv1.2とv1.3のCHANGELOGも見ておきましょう。 以下のように記述されています。

/****************************** v1.3 *********************************/

Added:
 * Support for arm64 and arm64e targets

Changed:
 * Updated TAPI library to 1100.0.11

/****************************** v1.2 *********************************/

Added:
 * Support for ld64 '-lto_library <path>'

Changed:
 * Updated cctools to 949.0.1 and ld64 to 512.4

Removed:
 * Support for <= 10.5 SDKs. Use 'osxcross-1.1' branch for these.

osxcross-v1.2とosxcross-v1.3の配布はもう終了しているので関係なかろうと思われるかもしれませんが、 実はこの v1.1 と v1.5 の中間点として v1.3 に相当するビルドを行うことも可能なのです。 そのようなことをしたい場合はこの情報が参考になります。

要はv1.3に相当するapple-libtapi(1100.0.11) と cctools-port(949.0.1、512.4)をダウンロードしてビルドすればよいわけです (apple-libtapiとcctools-portについては、古いバージョンの配布もThomas Pochtrager氏のサイトでまだ行われています)。

build.sh については例えば osxcross-v1.5 のものを使い、 以下のように build.sh の内容を修正します。

  #get_sources https://github.com/tpoechtrager/apple-libtapi.git 1300.6.5
  get_sources https://github.com/tpoechtrager/apple-libtapi.git 1100.0.11

#CCTOOLS_VERSION=986
#LINKER_VERSION=711
CCTOOLS_VERSION=949.0.1
LINKER_VERSION=512.4

ただしこれだけではcctools-portのビルド中に若干のエラー(GNU configureのconfig.subが古いことによるエラー)が発生するかもしれません。 この config.sub を新しいものに置き換えるなど別途措置が必要になります。

MacOS SDK については 11.1(darwin20.2)と 10.13(darwin17)のどちらでもよいと思います。


目次に戻る

付属ライブラリlibresslについて

Moai システムでは HTTPS を実現するために、libressl ライブラリを使用しています。 Moaiのソースコード内にはこの libressl のソースコードも付属しています。 ただしVC8.0(VS2005)でもコンパイル可能とするため、若干コードを手直ししたものになります。

オリジナルのlibresslをそのままコンパイルしたい場合、VCだとより新しいバージョンが必要となります。 しかしVC9.0(VS2008)以降で生成されるバイナリはSubsystem Versions 5.0以降(Windows 98では動作しないもの)となってしまいます。 そのためVC8.0(VS2005)がこの意味ではボーダーとなるのです。

これに関しては以下の点に注意してください。

  • src フォルダ直下の make_world.bat または make_world.sh を初めて実行した場合、この libressl も自動的にビルドされます。

  • src フォルダ直下の make_world.bat または make_world.sh により clean を実行した場合、libresslの配下は clean されません。

    Note.

    proj_list_skip_clean.bat(proj_list_skip_clean.sh) というファイルによって、これらが clean されないように設定してあります。 なぜこのようにしているのかと言えば、libressl のコンパイルは時間がかかることと、Moai自体のバージョンが上がっても、libressl自体は通常はコンパイルし直す必要性はないものと考えられるからです。

  • libressl単体をビルドしたい場合は、libressl フォルダ内に入り、その中にある make_world.bat または make_world.sh を実行します。 尚、このフォルダ内で make_world.bat または make_world.sh により clean を実行した場合は、( src フォルダ直下の場合と異なり )今度は libressl 全体が clean されます。

Windows32bit版向けバイナリに関する2038年問題

libresslの場合、time_t型と呼ばれるデータ型を介し、標準ライブラリの時刻に関する関数が使われているため、 特に32bit版Windows向けのバイナリを作成する場合、2038年問題の対処について考慮する必要があります。 基本的にはこのtime_t型のデータサイズ(sizeof(time_t))の値が4の場合に問題が生じる可能性があり、 8の場合は万全となります(ただしtime_t型のサイズは標準Cライブラリの実装とも密接に関わるため、 単にマクロ等でtime_tの値を64bit型に強制的に置き換えるようなやり方では対処できません)。

尚、Windows以外のOS(例えばLinux)では、最近のコンパイル環境ではこれに関してデフォルトで対処されているため、 基本的に考慮する必要はありません。 また64bit環境ではそもそもこの問題は存在しません。

それぞれの(32bit向け)コンパイラ毎に対処方法を纏めますと以下のようになります (libtls-17.dll(Cygwinではcygtls-17.dll)がlibresslの実体となるファイルです)。

  • MinGW32.org

  • MinGW32.orgにおいてはsizeof(time_t)の値がデフォルトでは4となっています。 これを8に変える手段は一応存在しますが、その場合Microsoftが提供するランタイムライブラリmsvcrt80.dllが別途必要になります。 これをプログラムに同梱することは規約上できないため、 ユーザはMicrosoftのサイトから別途これをダウンロードしなければなりません。

    尚、znk_projectでのlibresslのビルドではmsvcrt80.dllを使うような形にしていません。 またznk_projectではlibressl内で用意されているマクロ SIZEOF_TIME_T の値を8と定義することでビルドを行っていますが、 このマクロを8にしてもsizeof(time_t)の値自体が8になるわけではありません。

    現状ではこれで特に何の問題も生じてはいませんが、crypto/compat/timegm.c内のtimegm関数で、 将来的にHTTPSの有効期限に関する不具合(証明書の上では有効期限内であるのに有効期限外と誤判定され、 安全側に振られて通信が遮断されるような現象)が発生するかもしれません。 そのため、より万全を期すならば znk_project/mkfalt/prebuild/windows-x86/libtls-17.dll を Moaiのインストールディレクトリ(moai-v2.3/bin/windows-x86)内へ上書きコピーした方がよいかもしれません。

  • MinGW64-32

  • MinGW64-32においてはsizeof(time_t)の値がデフォルトでは4となっています。 これを8に変えるには、__MINGW_USE_VC2005_COMPATマクロを定義した状態でコンパイルします。 またMinGW32.orgとは異なり、この場合msvcrt80.dllは不要です。 ただし出来上がる実行バイナリはWindows2000 SP4以降でしか動作しないものになります。

  • VC8.0(VS2005)

  • VC8.0においては、32bit版のclでコンパイルする場合であってもsizeof(time_t)の値が最初から8となっています。 そのため、この部分に関する2038年問題はそもそも存在せず、特に対処する必要はありません。 しかも標準ライブラリをスタティックリンクすることでmsvcrt80.dllも不要であり、 出来上がる実行バイナリはWindows98を含め、すべてのWindowsで動作します。

    しかしながら、オリジナルのlibresslをVC8.0(VS2005)でそのままビルドすることはできません (これはlibresslのソースコードが新しいC言語の機能(特に構造体の初期化)を使用して記述されているからです)。 znk_projectに同梱しているlibresslはVC8.0(VS2005)でもビルド可能なようにその部分を若干修正しています。 そのため、znk_projectに同梱しているlibresslを32bit版Windows向けにビルドするという条件に限るならば、 VC8.0(VS2005)が最善と言えるかもしれません(尚、VS2005にはいくつかエディションがありますが、 無償のExpressエディションで全く問題ありません)。

    ちなみに、znk_projectではlibresslのビルドがそもそも不可能であったり何らかの問題が懸念されるコンパイラ環境向けに、 ビルド済みライブラリとして znk_project/mkfalt/prebuild/windows-x86/libtls-17.dll を用意していますが、 これはVS8.0を使って我々がビルドしたものになります。

  • Cygwin32

  • Cygwin32においてはsizeof(time_t)の値が4となっており、 これを8に変える手段が基本的に用意されていません。 そのためCygwin32単独では、2038年問題に完全に対処するのは難しいと考えられます。

    Cygwin32でlibressl自体のビルドは可能であり、 またznk_projectではlibressl内で用意されているマクロ SIZEOF_TIME_T の値を8と定義することでビルドを行っていますが、 このマクロを8にしてもsizeof(time_t)の値自体が8になるわけではありません。

    現状ではこれで特に何の問題も生じてはいませんが、crypto/compat/timegm.c内のtimegm関数で、 将来的にHTTPSの有効期限に関する不具合(証明書の上では有効期限内であるのに有効期限外と誤判定され、 安全側に振られて通信が遮断されるような現象)が発生するかもしれません。 そのため、より万全を期すならば znk_project/mkfalt/prebuild/windows-x86/libtls-17.dll を cygtls-17.dll に改名したものを Moaiのインストールディレクトリ(moai-v2.3/bin/cygwin-x86)内へ上書きコピーした方がよいかもしれません。



目次に戻る

This article was written by:
none image

Mr.Moai

@znk project

none image

Zenkaku

@znk project