ビルドオートメーションツールGYPを使おう

GYP (Generate Your Projects) をご存知でしょうか。その名の通り、GYP はビルドツールで、ウェブブラウザの Chromium や JavaScript Engine の V8、そして JavaScript の実行環境に V8 を採用している Node.js など様々なプロジェクトで採用されています。 GYP はオープンソースです。Google Code で公開されており、誰でも手に入れることができます。 GYP は C++ をはじめ、ビルドを必要とする C や Objective-C を使用したプロジェクトにおいて、ビルドオートメーションを実現するために役に立ちます。 ただ、CMake や SCons に比べまだまだユーザが少なく、ドキュメントも豊富ではありません。そこで今日は GYP について紹介します。

GYP ってどんなもの?

例えばあなたが、C++ で書かれたスーパーエキセントリックでヘビーな、マルチプラットフォームをターゲットにしたゲームエンジンの開発者だとしましょう。数百、数千に渡るヘッダーファイルと、プラットフォームごとのインプリメントファイル、そしてそれらを検証するテストコードと、エンジンを初めて使う人のために用意したサンプルゲームのコードがあります。もちろんソースコードだけでは何の意味もありません。それらをビルドする必要があります。Visual Studio でビルドするためには Visual Studio 用のプロジェクトファイルが必要です。また Xcode でビルドするためには Xcode 用のプロジェクトファイルが必要になります。Linux でビルドするためには make ファイルを用意する必要があるでしょう。

無慈悲にもプロジェクトは拡大していく

今また、エンジンに新たなソースコードが加わりました!あなたは Visual Studio を開き、プロジェクトファイルに項目を追加します。すかさず OSX を立ち上げ Xcode のプロジェクトも変更します。ああ、次は make ファイルも編集しなきゃ。 新たに iOS, Android もサポートすることになりました。追加されたプラットフォームごとのインプリメントファイルや、テストコードも増え、いよいよ大変に。ユーザのために書いたチュートリアルのコードもビルドを前にして今となっては煩わしい。 もし、Visual Studio のプロジェクトや Xcode のプロジェクト、あるいは make ファイルを 1 つのテキストファイルで管理できたら。そして、コマンドをたたくだけでビルドに必要なファイルを自動生成することができたら、ずっと楽になるのに! その悩みを解消するのが GYP (Generate Your Projects) です。

GYP を手に入れよう

お使いのコンピュータに Subversion は入っていますか?もしすでにインストールされていれば話は早いです。Subversion を使って最新の GYP を手に入れることができます:

$ svn --version
$ svn co http://gyp.googlecode.com/svn/trunk gyp

もし、Subversion よりも Git を使うのが好みであれば、次のようにして入手できます:

$ git clone https://chromium.googlesource.com/external/gyp.git gyp

本来の GYP の Subversion リポジトリから git-svn で引っ張ってくることもできますが、あまりお勧めしません。なぜならとても時間がかかるからです1。 「それでもいいよ」と言う物好きな方のために git-svn を利用する方法も載せておきます:

$ git svn clone -s http://gyp.googlecode.com/svn gyp

EDIT (May 8, 2015)
この記事を書いた当時、GYP は Subversion (SVN) でバージョン管理されていましたが、 2015 年 2 月に、SVN から Git に移行し、リポジトリは googlecode から googlesource (https://chromium.googlesource.com/external/gyp) に移動しました。

Python 2.x をいれておこう

GYP は Python で書かれたソフトウェアです。GYP を使うには Python 2.x2 の実行環境が必要になるので一緒に手に入れておきましょう。Python 3 では上手く動作しないことがあるのでバージョンを確認しておきましょう:

$ python --version
Python 2.7.2

GYP のセットアップ

Python と GYP は手に入れましたか?手に入れた GYP は最初に一度だけセットアップする必要があります。

Linux や Mac OSX の場合

$ cd gyp
$ [sudo] python setup.py install

Windows の場合

$ cd gyp
$ python setup.py install

Linux や Mac OSX の場合、セットアップ後 gyp コマンドで GYP が使えるようになります。

$ which gyp
/usr/local/bin/gyp

GYP を使ってみよう

触ってみないとわからないものです。早速 GYP を使ってビルドしてみましょう。今回は、非常に小さな謎のライブラリ "Trivial Engine" とそのテストコードをビルドします。必要な GYP ファイルとビルド対象となるソースコードは次の5つです。

  • build/common.gypi
  • build/trivial.gyp
  • include/trivial/Entity.h
  • src/Entity.cpp
  • test/EntityTest.cpp

例として扱うサンプルコードは GitHub で公開しています。Git を使って最新版を入手することができます。

$ git clone git://github.com/mogemimi/gyp-getting-started.git

完全を期すため、ソースコードを載せておきます:

include/trivial/Entity.h

#pragma once

#include <string>

namespace Trivial {

class Entity
{
public:
    char const* GetName() const;
    void SetName(char const* name);

private:
    std::string name;
};

}// namespace Trivial

src/Entity.cpp

#include <trivial/Entity.h>

namespace Trivial {

char const* Entity::GetName() const
{
    return name.c_str();
}

void Entity::SetName(char const* name)
{
    this->name = name;
}

}// namespace Trivial

test/EntityTest.cpp

#include <iostream>
#include <trivial/Entity.h>

int main()
{
    static_assert(__cplusplus == 201103L, "C++11 supported.");
    Trivial::Entity entity;

    entity.SetName("Hello, GYP");
    std::cout << entity.GetName() << std::endl;

    return 0;
}

build/common.gypi

{
  'variables': {
    'conditions': [
      ['OS == "mac"', {
        'target_arch%': 'x64',
      }, {
        'target_arch%': '<(target_arch)',
      }],
    ],
  },
  'target_defaults': {
    'default_configuration': 'Release',
    'conditions': [
      ['target_arch == "arm"', {
        # arm
      }], # target_archs == "arm"
      ['target_arch == "ia32"', {
        'xcode_settings': {
          'ARCHS': ['i386'],
        },
      }], # target_archs == "ia32"
      ['target_arch == "mipsel"', {
        # mipsel
      }], # target_archs == "mipsel"
      ['target_arch == "x64"', {
        'xcode_settings': {
          'ARCHS': ['x86_64'],
        },
      }], # target_archs == "x64"
    ],
    'configurations': {
      'Debug': {
        'defines':['DEBUG=1'],
        'cflags': ['-g', '-O0'],
        'msvs_settings': {
          'VCCLCompilerTool': {
            'Optimization': '0', # /Od
            'conditions': [
              ['OS == "win" and component == "shared_library"', {
                'RuntimeLibrary': '3', # /MDd
              }, {
                'RuntimeLibrary': '1', # /MTd
              }],
            ],
          },
          'VCLinkerTool': {
            'LinkIncremental': '2',
          },
        },
        'xcode_settings': {
          'GCC_OPTIMIZATION_LEVEL': '0', # -O0
        },
      }, # Debug
      'Release': {
        'cflags': ['-O3'],
        'msvs_settings':{
          'VCCLCompilerTool': {
            'Optimization': '2', # /O2
            'InlineFunctionExpansion': '2',
            'conditions': [
              ['OS == "win" and component == "shared_library"', {
                'RuntimeLibrary': '2', # /MD
              }, {
                'RuntimeLibrary': '0', # /MT
              }],
            ],
          },
          'VCLinkerTool': {
            'LinkIncremental': '1',
            'OptimizeReferences': '2',
          },
        },
        'xcode_settings': {
          'GCC_OPTIMIZATION_LEVEL': '3', # -O3
        },
      }, # Release
    }, # configurations
    'variables': {
      'component%': 'static_library',
    },
  }, # target_defaults
}

build/trivial.gyp

{
  'includes': ['common.gypi'],
  'make_global_settings': [
    ['CXX','/usr/bin/clang++'],
    ['LINK','/usr/bin/clang++'],
  ],
  'target_defaults': {
    'msvs_settings': {
      'VCCLCompilerTool': {
        'WarningLevel': '4', # /W4
      },
    },
    'xcode_settings': {
      'GCC_VERSION': 'com.apple.compilers.llvm.clang.1_0',
      'CLANG_CXX_LANGUAGE_STANDARD': 'c++0x',
      'MACOSX_DEPLOYMENT_TARGET': '10.8', # OS X Deployment Target: 10.8
      'CLANG_CXX_LIBRARY': 'libc++', # libc++ requires OS X 10.7 or later
    },
  },
  'targets': [
    {
      'target_name': 'trivial_engine',
      'product_name': 'TrivialEngine',
      'type': 'static_library',
      'include_dirs': [
        '../include',
      ],
      'sources': [
        '../src/Entity.cpp',
      ],
      'xcode_settings': {
        'GCC_INLINES_ARE_PRIVATE_EXTERN': 'YES', # '-fvisibility-inlines-hidden'
        'GCC_SYMBOLS_PRIVATE_EXTERN': 'YES', # '-fvisibility=hidden'
      },
    },
    {
      'target_name': 'trivial_test',
      'product_name': 'TrivialTest',
      'type': 'executable',
      'dependencies': [
        'trivial_engine',
      ],
      'include_dirs': [
        '../include',
      ],
      'sources': [
        '../test/EntityTest.cpp',
      ],
      'xcode_settings': {
      },
    },
  ] # targets
}

おっと、逃げないでください!サンプルにしてはあまりにも長いコードに思わずびっくりしたかもしれませんが、ひとまずここは騙されたと思って、早速ビルドしてみましょう。

MSBuild (Visual Studio 2012) でビルドしてみよう。

Visual Studio 2012 のソリューションファイル、プロジェクトファイルを作ってみましょう。コマンドプロンプトまたは MinGW + MSYS を開き、次を実行します。(説明のために、GYP の場所を tools/gyp ディレクトリ3にしています。)

$ python tools/gyp/gyp build/trivial.gyp --depth=. -f msvs -G msvs_version=2012 -D target_arch=ia32

成功すると build ディレクトリ内に各プロジェクトファイルが生成されます。 生成されたソリューションファイルをビルドしてみましょう。先に MSBuild を利用するためにパスを通しておきます。環境に合わせて適宜置き換えてください。コマンドプロンプトを開き:

$ set Path=C:\Windows\Microsoft.NET\Framework\v4.0.30319;%PATH%

MSBuild を実行します。

$ MSBuild.exe build\trivial.sln

これでビルドができました!試しに動かしてみましょう。

$ build\Debug\TrivialTest.exe
Hello, GYP

ちなみに MSBuild でリリースビルドするには次を実行します。

$ MSBuild.exe build\trivial.sln /p:Configuration=Release

Xcode (Apple LLVM Clang++) でビルドしてみよう。

Xcode のプロジェクトファイルを生成してみましょう。 ターミナルを開き、

$ gyp build/trivial.gyp --depth=. -f xcode --generator-output=./build.xcodefiles/

ターゲットアーキテクチャを明示的に指定する場合は次のように target_arch 変数を指定します。

$ gyp build/trivial.gyp --depth=. -f xcode -D target_arch=ia32 --generator-output=./build.xcodefiles/
$ gyp build/trivial.gyp --depth=. -f xcode -D target_arch=x64 --generator-output=./build.xcodefiles/

Xcode のプロジェクトファイル build/trivial.xcodeproj が生成されます。ではビルドしてみましょう。

$ xcodebuild -project build.xcodefiles/build/trivial.xcodeproj

xcodebuild でリリースビルドを行う場合は次のようにします:

$ xcodebuild -project build.xcodefiles/build/trivial.xcodeproj -configuration Release

実際にビルドしたプログラムをターミナルから実行してみます。

$ build/build/Release/TrivialTest
Hello, GYP

感動しました? 初めて GYP をプロジェクトに導入してビルドが正常に通った日のことを僕は今でも鮮明に覚えています。 ええ、あれはとても晴れた日で、思わず感動して、えーと、そう、あの日の晩ごはんは確か…。

GYP を実際のプロジェクトで使ってみよう。

少々長くなりました(汗) GYP について少しでもおわかりいただけたでしょうか。GYP は非常に強力なビルドオートメーションツールです。 あなたのプロジェクトが今よりずっと規模が大きくなって複雑になったとしても、そのときはきっと役に立ってくれるはずです(巨大なソフトウェアである Chromium で使用されているくらいですから!)。

今回サンプルとして用意した .gyp ファイルは、実際に開発で使用しているファイルを流用したものです。 サンプルとして最小におさまるよう努力しましたが、できる限りこのまま本来のプロジェクトに使っていただけるような形にしています4。 もし次回機会があれば、お話できなかった GYP の機能について紹介します。

追記
GYP の具体的な使い方に関して、次の記事を書きました。ご参考になりましたら幸いです。


  1. およそ1~2時間くらい。 

  2. Python 2.6 または 2.7 がオススメです。Python ウェブサイトから取得できます。その他に chromium のリポジトリから Python を取得することもできます。Subversion を使って "svn co http://src.chromium.org/svn/trunk/tools/third_party/python_26@89111 tools/python" 

  3. V8 や node.js など GYP を採用しているプロジェクトでは、GYP 本体の場所を project-root/tools/gyp ディレクトリに指定するなどプロジェクトディレクトリ下にインストールすることも少なくないようです。 

  4. プロジェクトファイル生成時にターゲットアーキテクチャを指定できるように追加しています。また、C++11 を利用できるようにしています。その他、各プラットフォーム向けに必要な設定を行っています。 

Leave a Reply