読者です 読者をやめる 読者になる 読者になる

パンダのメモ帳

技術系のネタをゆるゆると

AngularJS + TypeScript でリッチなフォームを作ってみる

前回(AngularJS で Hello World)に引き続き AngularJS + TypeScript による簡単なアプリケーションを書いてみました。

今回は AngularJS を使ったフォームの例です。 AngularJS のみで、比較的簡単にリッチなフォームが作成できることがわかるんじゃないかなぁと思います。

まえおき

  • TypeScript で書いてます。
  • AngularJS のバージョンは2013年2月現在の最新安定版である 1.0.4 を使用します。
  • 自動受け入れテスト(E2E Test)は面倒なので省略しました。

ソースコードについて

なお、今回のソースコード一式も GitHub で公開しています。 また、こちらで実際に動作を確認することもできます。

解説

前回解説した内容についてはなるべく割愛します。

form 要素

<form id="signupForm" name="form"
  ng-controller="SignupForm.Controller" ng-submit="submit()" novalidate>

form 要素にいくつか属性を指定します。

  • スタイルシートでスタイルを指定するために id を指定しています。
  • AngularJS の FormController 機能を使うために name を指定しています。
  • ng-controller 属性で対応する Controller を指定しています。
  • ng-submit 属性 でフォームが submit される時の処理を指定しています。
    • ng-submit 属性を指定するとブラウザ標準の処理(送信)がキャンセルされ、 指定された処理のみが実行されるようになります。
  • novalidate 属性 は AngularJS と HTML5 の validation が衝突しないように指定しています。 AngularJS でフォームを制御する場合は書いた方がいいみたい。

テキストフィールド

<input type="text" name="username" ng-model="data.username"
  ng-minlength="8" ng-maxlength="16" ng-pattern="/^[a-zA-Z0-9]*$/" required>

シンプルなテキストフィールド。やっぱり見慣れない属性が並んでいます。

  • ここで指定した name 属性が FormController で使用されます。
  • ng-minlength, ng-maxlength, ng-pattern, required の各属性で AngularJS による validation を定義しています。
    • ng-minlength, ng-maxlength はその名の通り、 テキストフィールドに入力できる最小・最大の文字数を指定できます。
    • ng-pattern 属性 を使うことで、入力できるテキストを正規表現で制限することができます。
      • 今回はベタ書きしてますが、Scope内の変数を参照・指定することもできます。
    • required 属性 は AngularJS 独自の属性ではありませんが、 これを指定することで AngularJS 側でも必須と認識されます。
  • validation の状況に応じて ng-valid, ng-invalid, ng-dirty などの class が自動で付与されます。 これは実際に動いている状態で確認してみるのが早いと思います。

Validation と ng-show 属性

<span class="error" ng-show="form.username.$error.pattern">
  半角英数字のみで入力してください
</span>
<span class="error" ng-show="form.username.$error.minlength">
  8文字以上で入力してください
</span>
<span class="error" ng-show="form.username.$error.maxlength">
  16文字以内で入力してください
</span>
<span class="error" ng-show="form.username.$error.required">
  入力してください
</span>
<span class="valid" ng-show="form.username.$valid">
  OK
</span>

フォームの各コントロールに validation ルールを定義することで 上記の例のように validation 結果をリアルタイムで参照できるようになります。

今回は validation 結果を ng-show 属性で参照することにより、validation 結果に対応してメッセージを表示するようにしています。 このように ng-show 属性 を使うことで「記述した条件が true の場合のみ表示する」処理をかんたんに実現できます。

パスワード

<input type="{{textOrPassword()}}" name="password" ng-model="data.password"
  ng-minlength="8" ng-maxlength="16" ng-pattern="/^[a-zA-Z0-9]*$/" required>
(中略)
<input type="checkbox" id="passwordVisible" ng-model="passwordVisible">
<label for="passwordVisible">パスワードを表示する</label>
$scope.textOrPassword = () => {
  return $scope.passwordVisible ? 'text' : 'password';
};

パスワード入力フィールドの type 属性を動的にし、その値が checkbox の状態によって変わるようにすることで パスワードの表示・非表示を切り替える最近よくあるUIが比較的かんたんに実現できています。

フォームの入れ子

<p class="form-question">氏名:</p>
<div ng-form name="nameForm" class="form-answer">
  <input type="text" name="lastName" ng-model="data.name.lastName"
    class="name" placeholder="姓" required>
  <input type="text" name="firstName" ng-model="data.name.firstName"
    class="name" placeholder="名" required>
  
  <span class="error" ng-show="nameForm.$error.required">
    入力してください
  </span>
  <span class="valid" ng-show="nameForm.$valid">OK</span>
</div>

任意の要素に ng-form 属性 を付与することでフォームの入れ子構造が実現できます。 今回は氏名の「姓」と「名」を1つの子フォーム nameForm としています。 これにより複数要素の validation 結果を1つの子フォームの validation 結果として参照できるようになっています。

プルダウン

<select name="lang" ng-model="data.lang"
  ng-options="x.id as x.label for x in langs" required>
  <option value="">----------------</option>
</select>

ng-options 属性 で option 要素を動的に生成しています。 今回は lang に格納されている配列から option 要素の value となる id と表示するテキストの label を抽出しています。 ng-options 属性の書き方は他にもいくつかパターンがあるので 公式ドキュメントを読んでみてください。

ラジオボタン

<li ng-repeat="x in colors">
  <input type="radio" id="browser-{{x.id}}" value="{{x.id}}" ng-model="data.color">
  <label for="browser-{{x.id}}">{{x.label}}</label>
</li>

ng-repeat 属性 を使うと、配列(またはオブジェクト)を使用して要素を繰り返し動的に生成することができます。 今回はあらかじめ定義された配列 colors を使用して、動的にラジオボタンを生成しています。 ラジオボタンでは Model の状態に応じてチェック・非チェックが自動で制御されます。

チェックボックス

<li ng-repeat="x in ballGames">
  <input type="checkbox" id="ballGame-{{x.id}}" value="{{x.id}}" ng-model="data.ballGame[x.id]">
  <label for="ballGame-{{x.id}}">{{x.label}}</label>
</li>

ラジオボタン同様 ng-repeat 属性を使用してチェックボックスを動的に生成しています。 チェックボックスのチェック状況(ON/OFF)に応じて対応する Model の値が true/false に設定されます。

メールアドレス

<input type="email" name="email" ng-model="data.email" required>
<span class="error" ng-show="form.email.$error.email">
  正しいメールアドレスを入力してください
</span>

入力がメールアドレスであるかどうかの判定も AngularJS がやってくれます。

URL

<input type="url" name="url" ng-model="data.url">
<span class="error" ng-show="form.url.$error.url">
  正しいURLを入力してください
</span>

同様にURLかどうかの判定もできます。この辺は HTML5 だけでも同じことができますが AngularJS を使うことでよりリッチに、ブラウザ間の差異なく実現することができます。

フォームの繰り返し

<div ng-form name="phoneForm" class="phone" ng-repeat="phone in data.phone">
  <a href="" class="remove" ng-click="removePhone($index)">×</a>
  <select name="phoneType" ng-model="phone.type"
    ng-options="x.id as x.label for x in phoneTypes" required>
  </select>
  <input type="text" name="phoneNumber" class="phoneNumber" placeholder="電話番号"
    ng-model="phone.number" ng-minlength="10" ng-maxlength="11" ng-pattern="/^\d+$/" required>
  <span class="error" ng-show="phoneForm.phoneNumber.$error.pattern">
    半角数字のみで入力してください
  </span>
  <span class="error" ng-show="phoneForm.phoneNumber.$error.minlength">
    10桁以上で入力してください
  </span>
  <span class="error" ng-show="phoneForm.phoneNumber.$error.maxlength">
    11桁以内で入力してください
  </span>
  <span class="error" ng-show="phoneForm.phoneNumber.$error.required">入力してください</span>
  <span class="valid" ng-show="phoneForm.phoneNumber.$valid">OK</span>
  <span class="note">例)0312345678</span>
</div>
<div class="actions">
  <a href="" class="action" ng-click="addPhone()">電話番号を追加</a>
</div>

ng-repeat 属性と ng-form 属性を組み合わせて同じフォーム要素の繰り返しを実現しています。 こういった複雑なフォームをかんたんに実現できるのも AngularJS の魅力のひとつだと思います。

ng-disabled 属性

<button ng-disabled="form.$invalid">送信</button>

ng-show 属性同様「特定の条件を満たす場合のみ disabled」というコントロールを実現できます。 今回はフォーム全体の validation 結果が invalid である場合に送信ボタンを disabled にしています。

課題とか

今回は AngularJS の機能を使ってリッチなフォームを作ってみましたが、いくつか課題もあります。

  • 記述が長い
    • ViewHelper 的なオブジェクトを作ってなんとか記述を減らしたい。
  • サーバーとの通信(フォームデータの送信)は?
    • 今回はあえて省いた。$httpという AngularJS のサービスがあるので、それを使えば実現できる。
  • 複雑な validation が定義できない?
    • 他のコントロールに依存する validation など、複雑な validation が実現しにくい。
    • ng-required 属性を使えば「特定の条件を満たす場合のみ必須」といった条件は実現できる。
    • AngularUI を使えば好きな validation を定義できるようになるっぽい。

参考URL

AngularJS で Hello World

AngularJSGoogle が中心になって開発が進められている JavaScript MVC フレームワーク の一種です。もちろんオープンソース(MIT License)。 今回はこの AngularJS を使って Hello World するわけですが、 ただ世界にこんにちはするだけじゃおもしろくないので、次のようなカンジでやってみようと思います。

  • TypeScript で書いてみます。
  • ビルドツールに Grunt を使います。
  • Testacular + Jasmine を使って自動テスト(ユニットテスト、受入テスト)環境を構築します。
  • AngularJS のバージョンは2013年1月現在の最新安定版である 1.0.4 を使用します。

なお、今回のソースコード一式を GitHub で公開しています。 また、こちらで実際に動作を確認することもできます。

View を書く

まずは View となる index.html から。

app/index.html

<!DOCTYPE html>
<html ng-app>
  <head>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.4/angular.min.js"></script>
    <script src="js/main.js"></script>
    <link rel="stylesheet" href="css/main.css">
    <title>Hello World</title>
  </head>
  <body>
    <h1>AngularJS Example: Hello World</h1>
    <div ng-controller="HelloWorld.Controller">
      Input your name →
      <input type="text" ng-model="name" size="20">
      <hr>
      <p>{{greeting}} {{name}}!</p>
      <hr>
      <p><button ng-click="bye()">Bye!</button></p>
    </div>
  </body>
</html>

解説

<html ng-app>

まず html要素に ng-app属性 を指定し、この HTML が AngularJS を使用したアプリケーションであることを宣言します。

ちなみに AngularJS で使用する属性はこの ng-app属性のように ng- というプレフィクスがつきます。 他にも ng:appx-ng-app, data-ng-app などの書き方ができるみたいですが、 今回は公式のチュートリアルなどでも使用されている ng- 形式を使用します。

<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.4/angular.min.js"></script>

AngularJS は jQuery などと同様に Google の CDN で配信されているので、それを読み込みます。

<script src="js/main.js"></script>

アプリケーション本体となる JavaScript を読み込みます。あとで書きます。

<div ng-controller="HelloWorld.Controller">

使用する Controller を指定します。 ng-controller属性を指定した要素が Controller に対応する View ってことになるのかな?

<input type="text" ng-model="name" size="20">

input要素に ng-model属性を指定して Model に紐づけます。

<p>{{greeting}} {{name}}!</p>

Model の内容を View で表示するためには二重波括弧で囲った Angular Expression を記述します。 単にそのまま表示するだけならこんなカンジ。

<p><button ng-click="bye()">Bye!</button></p>

button要素に ng-click属性を指定してクリック時の動作を指定します。

Controller を書く

TypeScript で Controller を書きます。

app/js/main.ts

/// <reference path="../../lib/angularjs/angular.d.ts" />
module HelloWorld {
  export interface Scope extends ng.IScope {
    name: string;
    greeting: string;
    bye: () => void;
  }
  export class Controller {
    constructor($scope: Scope) {
      $scope.name = 'World';
      $scope.greeting = 'Hello';
      $scope.bye = () => $scope.greeting = 'Good-bye';
    }
  }
}

解説

/// <reference path="../../lib/angularjs/angular.d.ts" />

TypeScript で記述するにあたり AngularJS の型定義ファイル(*.d.ts)が必要になりますが TypeScript用の各種モジュール定義ファイルを GitHub で公開してくれている人がいる ので、ありがたく使わせていただきます。

今回は lib ディレクトリ以下に必要な分だけコピーして使用しています。

export interface Scope extends ng.IScope {
  name: string;
  greeting: string;
  bye: () => void;
}

AngularJS の Controller は Scope と呼ばれるオブジェクトを受け取り、そこに Model となるプロパティを設定する……っぽい。 TypeScript なので Controller が扱う Scope にどんなプロパティがあるのか定義してあげる必要があります。

export class Controller {
  constructor($scope: Scope) {
    $scope.name = 'World';
    $scope.greeting = 'Hello';
    $scope.bye = () => $scope.greeting = 'Good-bye';
  }
}

Controller 本体。各プロパティの初期値を設定します。 ちなみに JavaScript で書くならこんなカンジかな?

var HelloWorld = {
  Controller: function($scope) {
    $scope.name = 'World';
    $scope.greeting = 'Hello';
    $scope.bye = function() {
      $scope.greeting = 'Good-bye';
    };
  }
};

動かしてみる

app/index.html をブラウザで開き、テキストフィールドを変更すると即座に View が更新されます。 ここでも確認できます。 AngularJS が持つパワーの片鱗が感じられると思います。多分。

ユニットテストを書く

AngularJS は自動テストをかなり考慮して設計されており DI (Dependency Injection) をサポートする機能が組み込まれています。

また Testacular という Google 製の JavaScript テストランナーとも仲良しです。 ……というよりも、AngularJS のために Testacular を作った、というのが正しいみたいです。

Testacular は単なるテストランナーなのでユニットテストフレームワークには Jasmine を使います(Mocha を使ったりすることもできるみたいです)。

というわけで AngularJS + Jasmine + TypeScript でユニットテストを書いてみました。

test/unit/HelloWorldSpec.ts

/// <reference path="../../lib/jasmine/jasmine.d.ts" />
/// <reference path="../../lib/angularjs/angular.d.ts" />
/// <reference path="../../lib/angularjs/angular-mocks.d.ts" />
/// <reference path="../../app/js/main.d.ts" />
describe('HelloWorld', () => {
  var $rootScope: ng.IRootScopeService,
      $controller: ng.IControllerService,
      controller: HelloWorld.Controller,
      scope: HelloWorld.Scope;

  beforeEach(inject( ($injector: ng.auto.IInjectorService) => {
    $rootScope = $injector.get('$rootScope');
    $controller = $injector.get('$controller');
    scope = <HelloWorld.Scope>$rootScope.$new();
    controller = $controller(HelloWorld.Controller, {'$scope': scope});
  }));

  describe('Controller', () => {
    it('initialize scope', () => {
      expect(scope.greeting).toEqual('Hello');
      expect(scope.name).toEqual('World');
      expect(typeof scope.bye).toBe('function');
    });
  });

  describe('Scope', () => {
    it('.greeting should be "Good-bye" when called bye()', () => {
      scope.bye();
      expect(scope.greeting).toEqual('Good-bye');
    });
  });
});

解説

/// <reference path="../../lib/jasmine/jasmine.d.ts" />
/// <reference path="../../lib/angularjs/angular.d.ts" />
/// <reference path="../../lib/angularjs/angular-mocks.d.ts" />
/// <reference path="../../app/js/main.d.ts" />

TypeScript なので依存するモジュールの定義ファイルを指定します。 ここでは Jasmine と AngularJS 本体、さらに angular-mocks.js の定義を読み込んでいます。

beforeEach(inject( ($injector: ng.auto.IInjectorService) => {
  $rootScope = $injector.get('$rootScope');
  $controller = $injector.get('$controller');
  scope = <HelloWorld.Scope>$rootScope.$new();
  controller = $controller(HelloWorld.Controller, {'$scope': scope});
}));

↑の部分で Controller が依存する Scope のモックを注入しています。

ここまで抑えておけばあとは普通にテストを書くだけなので残りは割愛。 ちなみにテスト結果はこんな感じ

Testacular を使ったコマンドラインでのテスト実行方法は後ほど。

受入テストを書く

AngularJS には受入テスト(End-to-End Test)用のフレームワークが組み込まれています。 書き方が Jasmine っぽいけど Jasmine じゃないのが落とし穴なので注意。

受入テストも TypeScript で……と言いたいところですが 使用する angular-scenario.js のモジュール定義ファイルが見当たらなかったので今回は諦めて JavaScript で書きました。

test/e2e/scenarios.js

describe('HelloWorld', function() {
  beforeEach(function() {
    browser().navigateTo('../../app/');
  });
  it('should change the binding when user enters text', function() {
    expect(binding('name')).toEqual('World');
    input('name').enter('AngularJS');
    expect(binding('name')).toEqual('AngularJS');
  });
});

test/e2e/runner.html

<!doctype html>
<html lang="en">
  <head>
    <title>End2end Test Runner</title>
    <script src="../../lib/angularjs/angular-scenario.js" ng-autotest></script>
    <script src="scenarios.js"></script>
  </head>
  <body>
  </body>
</html>

解説

beforeEach(function() {
  browser().navigateTo('../../app/');
});

各テストケースの実行前に、対象のページを開くようにします。

it('should change the binding when user enters text', function() {
  expect(binding('name')).toEqual('World');
  input('name').enter('AngularJS');
  expect(binding('name')).toEqual('AngularJS');
});

input要素の入力前後で {{name}} の部分が変更されることを確認します。

その他、End-to-End Test に関するドキュメントはこちらを参照してください。

ちなみに、End-to-End Test を実行するためには実際に http で対象のページにアクセスする必要があります。 今回のサンプルでは Node.js を使ってテスト用の HTTPサーバーを立ち上げることができます。

$ cd /path/to/project
$ npm install            # 初回のみ
$ npm start

> angularjs-example-helloworld@0.0.1 start /path/to/project
> node server.js start

server.js daemon successfully started

サーバーが立ち上がるとhttp://localhost:8000/test/e2e/runner.htmlでテストを実行できます。 ちなみにテスト結果はこんな感じ

Grunt について

今回は TypeScript のコンパイルと、コマンドラインからテストを実行するために Grunt を使用しています。 grunt.js はこんなカンジです。 全部説明すると長くなるので、使用したプラグインだけ紹介します。

  • grunt-contrib-clean
    • 一時ファイルなどを消してくれる clean タスクが使えるようになります。
  • grunt-typescript
    • TypeScript をコンパイルする typescript タスクが使えるようになります。
  • gruntacular
    • Testacular によるテストを実行する testacular タスクが使えるようになります。

npm install でプラグインがインストールされるようになっているので、↓こんなカンジで使ってください。

$ cd /path/to/project
$ npm install            # 初回のみ
$ grunt                  # TypeScript のコンパイル
$ grunt unit-test        # TypeScript のコンパイル → 自動ユニットテスト実行
$ grunt e2e-test         # TypeScript のコンパイル → 自動受け入れテスト実行
$ grunt test             # TypeScript のコンパイル → 全テスト実行

テストのブラウザには PhantomJS を使用しているのでインストールするか test/config 以下にある各 conf.js ファイルの下記の部分を編集してください。

browsers = ['PhantomJS'];

PhantomJS の他には Chrome とか ChromeCanary とか Firefox とか使えるみたいです。 複数指定もできます。

最後に

TypeScript 使おうとか、テスト周りまでちゃんと調べてたらすごく疲れた。 AngularJS はまだ日本語の情報があんまりないイメージなので、みんなどんどん使ってどんどんアウトプットすればいいと思う。

参考URL

Android 開発者が読んでおくべき6つの記事 #AndroidAdvent2012

本エントリーは Android Advent Calendar 2012 23日目(表)の記事となります。

まえがき

参加表明してから約1ヶ月、どんな記事にするべきか悩みましたが、結局これといったネタが思いつかなかったのと、ももクロネタは案の定 @sobachanko 氏に先を越されたので、僕がこれまで読んでタメになった Android 関連の記事をいくつか紹介してお茶を濁すことにしました。

ちなみに Android Advent Calendar には裏もあります。 本日の裏は @STAR_ZERO さんの「DeployGateについて」です。 裏22日目の @bols_blue さんの「DeployGatePluginを作ってみました。」と併せて読みたいですね。表と裏、裏の方がガチっぽいのは気のせいでしょうか。

Android 開発者が読んでおくべき6つの記事

というわけで本題。どれも Android 開発をするデベロッパーが読んでおくと幸せになれる記事ばかりです。 これから Android 開発を始める人はもちろん、現役バリバリの Android デベロッパーも初心を思い出すために読み返してみると再発見があったりするかもです。

Performance Tips | Android Developers

Android 開発者向け公式サイトの記事。 クアッドコア端末が発売されるなど端末の性能向上はめざましいですが、アプリの応答性というのは依然重要な要素です。 昔読んだときと少し内容が変わっている気がするので、昔読んだという人も読み返してみてください。

AsyncTaskを使った非同期処理のきほん | クラスメソッド開発ブログ

アプリの応答性といえば AsyncTask による非同期処理は欠かせません。 Honeycomb(3.0) 以降であれば AsyncTaskLoader も使えますがやっぱり基本は AsyncTask です。 通信やデータベース処理、ファイル入出力を伴う処理は、ちゃんと非同期化しましょう。

Google I/O 2010 - The world of ListView

2010年の Google I/O で行われた講演の動画。英語なので何を言ってるのかほとんど分かりませんが、ありがたいことに @yanzm さんが日本語でまとめてくれています。 ListView (ListActivity, ListFragment) を使ったアプリを作るなら抑えておくべきポイントがまとまっています。

Y.A.M の 雑記帳: Android Fragment を使う

これもまた @yanzm さんのブログから。 Honeycomb(3.0) から利用可能, Android Compatibility Package を使えば Donut(1.6) 以降で利用可能な Fragment の基礎がまとまっています。 ついでにこっちの記事 も併せて読めば Fragment ももう怖くない。

【総集編】デザイナーがコードから読み解く、Androidアプリのデザインの幅を広げるコツとTips

全4回に渡って行われた Android App Designs の総集編で使われた @tommmmy さんによるスライドです。 デザイナーさんと一緒に仕事するときに気をつけるべきポイントがまとまっているので、デザイナーさんに dis られないためにもぜひ読むべき。 一人でアプリを作る上でも役立つ情報・テクニックが満載です。

Android開発を行う際の eclipse の便利設定 (zaki日記)

去年の Android Advent Calendar で @zaki50 さんが書いてくださった記事です。 なんとなく Eclipse + ADT による開発環境を構築しただけじゃ気付かないような便利設定をまとめてくれています。 この記事にあるように Eclipse を設定することで、開発効率が劇的にアップすること間違いなし。

あとがき

個人的な2012年は @R246 ことあおやまさんによってももクロ色に染められたり、第4回シャープハッカソン@東広島に招待していただいたりと、充実した1年でした。 最近は Android よりも Node.js, TypeScript, Scala 辺りにうつつを抜かしてたりしますが、来年も充実した1年になりますように。

最後に、昨年に引き続き Android Advent Calendar を企画・運営してくれた @youten_redo さんに感謝!

CentOS 6.3 に Ruby 1.9.3 をインストールする

CentOS 6.3 に yum を使って Ruby の標準パッケージをインストールする場合、バージョンは1.8系の1.8.7となる。 ところが一部の Ruby 製アプリケーション(ex. Gitlab )では1.9系が求められるケースがあるため、 今回はソースから Ruby の最新版をインストールすることにした。

依存パッケージをインストール

Ruby のコンパイルに必要なパッケージをインストールする(要EPEL)。

[user@localhost ~]$ sudo yum install --enablerepo=epel make gcc zlib-devel openssl-devel readline-devel ncurses-devel gdbm-devel db4-devel libffi-devel tk-devel libyaml-devel

ダウンロード → コンパイル

Ruby のダウンロードのページから1.9系の最新版をダウンロードして解凍、コンパイルする。 2012年12月20日現在の最新版は 1.9.3-p327 だった。

[user@localhost ~]$ cd /usr/local/src
[user@localhost src]$ wget ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.3-p327.tar.gz
[user@localhost src]$ tar xf ruby-1.9.3-p327.tar.gz
[user@localhost src]$ cd ruby-1.9.3-p327
[user@localhost ruby-1.9.3-p327]$ ./configure --prefix=/usr/local
[user@localhost ruby-1.9.3-p327]$ make

RPM化 → インストール

コンパイルに成功したら checkinstall を使って RPM化してインストールする(checkinstall のインストール手順はこちら)。

[user@localhost ruby-1.9.3-p327]$ sudo checkinstall --fstrans=no
(中略)
**********************************************************************

 Done. The new package has been saved to

 /root/rpmbuild/RPMS/x86_64/ruby-1.9.3-p327-1.x86_64.rpm
 You can install it in your system anytime using: 

      rpm -i ruby-1.9.3-p327-1.x86_64.rpm

**********************************************************************
[user@localhost ruby-1.9.3-p327]$ sudo rpm -ivh --nodeps /root/rpmbuild/RPMS/x86_64/ruby-1.9.3-p327-1.x86_64.rpm

checkinstall のオプションに --fstrans=no を付けないとエラーになる。 また、checkinstall 実行時「それらをパッケージから除外しますか?(yesと答えることをおすすめします) [y]:」に素直に yes と答えると処理が進まなくなる罠がある。no と答えても問題ないのでここは no と答えておく。 さらに rpm のインストール時に --nodeps を指定しないとエラーになるので注意。

参考

CentOS 6.3 に checkinstall をインストールする

64bit版 CentOS 6.3 に checkinstall をインストールする。

要点

  • 64bit版 CentOS 6.3 では、2012年12月19日現在の最新版 1.6.2 をダウンロードして make しようとしてもエラーで失敗する。
  • 公開されている git リポジトリから最新版を clone して一部を修正し、コンパイル → インストールする。

依存パッケージをインストール

checkinstall をコンパイル・実行するためには gettext をインストールする必要がある。

[user@localhost ~]$ sudo yum install gettext rpm-build

ソースコードを入手 → 修正

http://checkinstall.izto.org/ で公開されている git リポジトリから ソースコード一式を入手しファイルを修正する。

[user@localhost ~]$ cd /usr/local/src
[user@localhost src]$ git clone http://checkinstall.izto.org/checkinstall.git
[user@localhost src]$ cd checkinstall
[user@localhost checkinstall]$ vi Makefile
[user@localhost checkinstall]$ vi checkinstallrc-dist
[user@localhost checkinstall]$ vi installwatch/Makefile

修正後の diff は次の通り。

[user@localhost checkinstall]# git diff
diff --git a/Makefile b/Makefile
index 2e28adc..b6d217c 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@
 PREFIX=/usr/local
 BINDIR=$(PREFIX)/sbin
 LCDIR=$(PREFIX)/lib/checkinstall/locale
-CONFDIR=$(PREFIX)/lib/checkinstall
+CONFDIR=$(PREFIX)

 all:
        for file in locale/checkinstall-*.po ; do \
diff --git a/checkinstallrc-dist b/checkinstallrc-dist
index d4feb4e..fe5f56e 100644
--- a/checkinstallrc-dist
+++ b/checkinstallrc-dist
@@ -117,7 +117,7 @@ RESET_UIDS=0
 NEW_SLACK=1

 # Comma delimited list of files/directories to be ignored
-EXCLUDE=""
+EXCLUDE="/selinux"

 # Accept default values for all questions?
 ACCEPT_DEFAULT=0
diff --git a/installwatch/Makefile b/installwatch/Makefile
index ae34fc1..fb41eb3 100644
--- a/installwatch/Makefile
+++ b/installwatch/Makefile
@@ -11,7 +11,7 @@ PREFIX=/usr/local
 VERSION=0.7.0beta7

 BINDIR=$(PREFIX)/bin
-LIBDIR=$(PREFIX)/lib
+LIBDIR=$(PREFIX)/lib64

 all: installwatch.so

コンパイル → インストール

修正が完了したら make → make install でインストール。

[user@localhost ~]$ make
[user@localhost ~]$ sudo make install

実行

まずは checkinstall 自体を rpm 化して上書きインストールする。 /root/rpmbuild/SOURCES というディレクトリが必要になるので先に作成しておく。

[root@localhost ~]# mkdir -p ~/rpmbuild/SOURCES
[root@localhost ~]# cd /usr/local/src/checkinstall
[root@localhost checkinstall]# checkinstall

いくつか入力を求められるので入力していくと次のような結果が表示される。

**********************************************************************

 Done. The new package has been saved to

 /root/rpmbuild/RPMS/x86_64/checkinstall-20121220-1.x86_64.rpm
 You can install it in your system anytime using: 

      rpm -i checkinstall-20121220-1.x86_64.rpm

**********************************************************************

生成された rpm をインストール&確認する。

[root@localhost ~]# rpm -ivh /root/rpmbuild/RPMS/x86_64/checkinstall-20121220-1.x86_64.rpm
準備中...                ########################################### [100%]
   1:checkinstall           ########################################### [100%]
[root@localhost ~]# rpm -qi checkinstall
rpm -qi checkinstall
Name        : checkinstall                 Relocations: (not relocatable)
Version     : 20121220                          Vendor: (none)
Release     : 1                             Build Date: 2012年12月20日 00時04分10秒
Install Date: 2012年12月20日 00時09分59秒      Build Host: localhost
Group       : Applications/System           Source RPM: checkinstall-20121220-1.src.rpm
Size        : 453050                           License: GPL
Signature   : (none)
Packager    : checkinstall-1.6.3
Summary     : CheckInstall installations tracker, version 1.6.2
Description :
CheckInstall installations tracker, version 1.6.2

CheckInstall  keeps  track of all the files created  or
modified  by your installation  script  ("make install"
"make install_modules",  "setup",   etc),   builds    a
standard   binary   package and  installs  it  in  your
system giving you the ability to uninstall it with your
distribution's  standard package management  utilities.

参考