Qt(C++)用のパッケージマネージャ(qtpm)作ってます。

  • 4
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

mkDDA9cTAt.gif

qiitaだとアニメGIF再生されないのかな・・・ここを見てください。

http://g.recordit.co/mkDDA9cTAt.gif

ここ一週間ぐらいパリに出張に行っているので(意訳: アサシンクリードユニティを夜遅くまでプレイしているので)止まっていますが、Qt用のパッケージマネージャを作っています。昨年もJSXでパッケージ管理というネタでアドベントカレンダー書いたので、2年連続でパッケージ管理ネタですね。

進捗としては、依存関係のあるものを引っ張ってくる部分が未完成なのと、ブランチ名で取ってくるところがまだ未実装ですが、スクリーンキャストの通り、zlibをとってきてローカルでビルドして使えるようにするぐらいはできます。

ソースはこちら: https://github.com/qtpm/qtpm

このエントリーではどんな設計指針でやっているか、どうしてそうしたかという考えについて書きます。ソースコードは出てこないゆるふわポエムです。

何を管理したいのか?

MacPortsなんかを使っていればzlibが欲しい、みたいなのはすぐに解決できます。/opt/local/lib以下に必要なものが入ります。でも、C++等で作成したバイナリプログラムを配布したいときに、その手のOSのパッケージシステムで入れたライブラリが邪魔になったりします。また、個別のインストーラで入れた言語ランタイム(VB6.0ランタイムとか)をありがたく各プログラムから使うような時代ではなく、プログラムを配るときには必要なライブラリを全部バンドルしますよね?じゃあ静的リンクでいいんじゃないですかね?ということで、必要なライブラリを集めてきて静的リンクライブラリを作ってしまえ、というのが最初のゴールです。Windowsでlibssh2とかOpenSSLとかインストールめんどいですよね。

またそれだけではなく、Qtにもいろいろ便利なライブラリを提供してくれている人はいるのですが、お前らなんでもかんでもzlibのソースをリポジトリに入れるんじゃない!とか、逆に必要なライブラリ全部自分で入れなきゃいけないものもあったり、おもてなし具合はさまざま。これらのQt特化のライブラリは逆にコンパイルしておかなくても、必要なファイル群が.priにかかれていて、プロジェクトファイルにincludeできれば十分です。デバッガで他のコードと同じように追いかけられたら便利ですよね?そういうソースコードとしてインストールできるとうれしいです。

後は将来的にはクロスコンパイルですよね。AndroidとiOS用にライブラリ用意しなきゃいけないとか、めんどいですよね。これらが簡単になればいいな、というのが出発点です。

参考にしたもの

Python(PyPI)、Node.js(npm)、JSX(npmにあいのり)、golang(単にgithub)あたりは実際にパッケージのアップもしていますので、このあたりを中心に参考にしつつclibCPMなども軽くみつつどうするか決定しました。

ローカルでの一元管理 vs 分散管理

Python、Ruby、Golang、C/C++のライブラリなどは各環境ごとに固有のファイル置き場があって、パッケージを入れるとグローバルに影響を与えることになります。複数のバージョンの共存もできません。そのため各言語では◯◯envとか◯◯mvみたいな補助ツールを使って、ライブラリセット(サンドボックス)を複数取り替えたりしています(+αで処理系のバージョンを切り替えたりも)。Pythonに関しては、3.4でisolated modeというのが入って、グローバルなサードパーティ製モジュール置き場(site-packages)をモジュール読み込みパスから外すことができるようになりました。

Node.jsのnpmはグローバルに置くこともできますが(-gオプション)、デフォルトではローカルのnode_modulesフォルダに置きます。作業フォルダが変われば別々にライブラリのコピーが作られます(ダウンロード自体はキャッシュしていてModifiedを見て余計なダウンロードはしないようになっています)。

npmはさらに独特な点があり、他はフラットにモジュールが置かれるのですが、node_modulesが入れ子にできるようになっています。例えば作成中のプログラムでnodetarの1.0以上を使うと直接指定していて、さらに別のライブラリ(Aとする)がこれと互換性のない0系のnodetarを使っていたとすると、これが別々にインストールされます

  • ルート/node_modules/tar (1系)
  • ルート/node_modules/A/node_modules/tar (0系)

この場合、前者のPackクラスと、後者のPackクラスは名前が同じでも別インスタンスになります。gitで直接指定されたパッケージがあると互換性があっても別々にインストールされたりいろいろトラブルがあったりするのですが、それはまた別の機会に。

qtpmでは、環境によって使えるライブラリのバージョンが固定されていたり、制約があったりする中で固定するのは難しいだろうということで、npmスタイルになっていますが、C++ではソースコード中に書かれたnamespace以外の論理的な名前空間はありません。外部から名前空間をコントロールできません。同じモジュールを複数バージョン読み込んだら、コンパイル時はパスを厳密に呼び分けることでなんとか通るかもしれませんが、リンク時にはエラーになります。なのでひとつのワークフォルダ内では同じパッケージの別バージョンは許さないようにしています。

リモートでの一元管理 vs 分散管理

PyPI(ぱいぴーあい)、CPAN、RubyGemsなんかは一元管理されたパッケージリポジトリがあって、各モジュールがユニークな名前を持っていて名前で必要なライブラリを依存関係も含めて取得してきます。リモートのモジュールが一元化されています。

npmも、それらと同じようなリポジトリを持っていますが、gitリポジトリから直接取ってくることもできます。もしもグローバルなリポジトリにすでに登録されている名前のモジュールであっても、別バージョンを自分のgithubやbitbucketの中で作って、それを使うことができます。

golangはさらにそれを推し進めていて、パッケージリポジトリはなく、gitやbzr、hgなどのバージョン管理システムでとってきた各フォルダをパッケージとして使うことができます。ただし、それでは不便なのか、Wikiで有名ライブラリ情報がまとめられていたりします。

このツールではnpm的に、分散も、グローバルも両方扱えるようにしたいな、と思っています。グローバルはとりあえずgithubのWikiをリポジトリ情報としていて、gitのリポジトリへのリンクを取得してくるようにしています。将来的にはもっとかっこいいウェブサービスにしたいですね。ダウンロード数とか集計したいし。

一部のライブラリを別実装に置き換えたい

npmでたまに大規模開発していると問題になったりするのですが、子モジュールから使われている孫モジュールの一部に手を加えたいということがあります。機能が足りないとか、実装を高速化したいとかです。npmだとローカルのパッケージ等を指定しても、孫が子モジュールからgitなどで指定されてれば手が出ません。子孫全部で別バージョンを用意すればなんとかなりますが、やりたいことに対してやらなければならない手数が多くて大変ですよね。

このパッケージマネージャでは、gitだろうが、ローカルだろうが、バージョン指定されたモジュールだろうが、「名前」が絶対のものとして扱われるようになっています。最上位のプログラムで、ローカルのパッケージを指定すればそれが最優先で扱われます。

優先順位としては、

  • ローカルのディレクトリ = ローカルの圧縮ファイル > リモートのリポジトリ(ブランチ指定) > リモートのリポジトリ(バージョン指定)

です。何も指定しなければ、とってきた最新のバージョンに対するバージョン指定になります。

どんな動作するの?

  1. ワークフォルダ内にdeps.[ビルドターゲット]という名前のフォルダを作ります。ターゲットはqmakeでサポートしている名前です。
  2. ワークフォルダの.qtpm以下にフォルダを用意する。gitならcloneしてきたり、アーカイブファイルなら展開したりとか。ただしローカルにすでにあるフォルダならそのまま使います。
  3. パッケージ内のqtpackage.iniファイルを読んで、処理方法を決めます。ビルドが必要なものはビルド方法(今のところcmakeとconfigure)が書かれていますので、その通り実行します。--prefixは出力先のdeps.[ビルドターゲット]です。
  4. ソースをそのまま提供するものは、出力先フォルダにそのままコピー・・・かな。詳細は未定。
  5. パッケージ内のモジュール名.priファイルを出力先フォルダにコピーします。
  6. 出力先フォルダをスキャンして、下記のようなqtpackage.priファイルをワークフォルダに作ります。

    qtpackage.pri
    macx-clang {
        INCLUDEPATH += $$PWD/deps.macx-clang/include
        LIBS += -L$$PWD/deps.macx-clang/lib
        include(deps.macx-clang/zlib.pri)
    }
    
  7. ワークフォルダの.proファイルに、include(qtpackage.pri)という行を足します。

今後の予定

自分で欲しいという目的があるのでのんびりじっくり作る予定です。

年末年始はリリースされるゲームが多くて大変ですよね。ベヨネッタ2も積んでるし、妖怪ウォッチもポケモンXもプレイし始める前に続編出ちゃうし、ソニックは2本出るし、The Crewも気になるし、アサシンクリードももう一本出るし・・・・

最後のQt特有部分のpriとproに対する処理をちょっと変えれば他のツールに対しても使えそうな気はしていますが、そこはしばらく手は出さないかなぁ・・・今のところ必要はないので。

明日も僕が書く予定です。qtpmを作る時に見つけたネタです。

この投稿は Qt Advent Calendar 20141日目の記事です。