ChocolateyのApproved Packageを作成する手順

この記事はPowerShell Advent Calendar 2014の12/20の記事です。

Windowsのデファクトとパッケージマネージャーとして知るぞ知る人に活用されてきたChocolateyですが、Windows 10から公式のパッケージマネージャであるOneGetが用意されることになり、サポートリポジトリとしてChocolateyのリポジトリが最初にサポートされることになりました。

この方針によるものか不明ですが、いままでcpush時の自動チェックさえ通れば、自由に作成することができたChocolateyのパッケージにmoderatorによるapproveというものが導入され、moderatorによりapproveされないパッケージは下記のようなメッセージが表示され、信頼性の低いパッケージという位置づけになってしまいました。

This package was submitted prior to moderation and has not been approved. While it is likely safe for you, there is more risk involved.

私も2つパッケージを作っているので、なんとか正式のみなさんに安心して使って貰えるようパッケージにしたいということでJDK 8JRE 8は今のところ自分が作っているものしかないため、2つ修正して申請してみました。JDK 8はスルーされてしまっていますが、JRE 8はシェアの関係か何度かのrejectの結果、無事approveされましたので、今後ChocolateyおよびOneGetのパッケージを作成される方に参考になればと思い、approveに必要な観点を解説させて頂きます。

1. *.nuspec

*.ps1ファイルの前に、パッケージの定義で指摘を受けることがあります。

1.1. <title>と<version>

JRE 7とJRE 8のように同一製品で複数のバージョンがある場合も含めて、<title>にはバージョンを入れないように指摘を受けました。また、バージョンには個々の製品で規則がありますので、同一製品の別バージョンのパッケージがある場合、それに習いましょう。JRE 8の場合、他のJREパッケージのメンテナーからの提案もあり下記のように修正しました。

<title>Java SE Runtime Environment</title>
<version>8.0.25.03</version>

最後の.03のところはJRE自体に沿っていませんが、これはパッケージ自体に問題があった場合に同一バージョンをpushできないので苦肉の策としてつけたものです。特にここまでは文句は言われないようです。

1.2. <iconUrl>

アイコンはそれらしいのを適当に指定するのではなく、同一製品の別バージョンのパッケージと合わせる必要があります。また、フォーマットはPNGではなくSVG等ベクターフォーマットが望ましいとのことです。こちらも指摘を受けて修正しました。

2. *.ps1

2.1. ファイルの分割

インストール用のchocolateyInstall.ps1があればどのように分割しても特に問題はないようです。

2.2. Chocolatey提供APIの使用

Chocolateyが提供しているAPI(コマンドレット、関数)の機能の範疇では自分でゴリゴリ書かずにChocolateyのAPIを使用するようにという指摘がきます。

ChocolateyのAPIはChocolatey Utility Functions aka Helpers Referenceで解説されています。

例えば、JRE 8では、以下のようにクッキーとServerCertificateValidationCallbackの制御をする必要があった元々のJDK 8の制限を引き継いで”Net.WebClient”を使用していました。

function download-from-oracle($url, $output_filename) {
    if (-not (has_file($output_fileName))) {
        Write-Host "Downloading JDK from $url"
        [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
        $client = New-Object Net.WebClient
        $dummy = $client.Headers.Add('Cookie', 'gpw_e24=http://www.oracle.com; oraclelicense=accept-securebackup-cookie')
        $dummy = $client.DownloadFile($url, $output_filename)
    }
}

しかし、後述しますが、JRE 8では認証が不要なサイトでダウンロードできるようになったため、ChocolateyのAPIを使用するように指摘が来ました。下記のようにChocolateyのAPIを使うように書き換えました。

function download-from-oracle($url, $output_filename) {
    if (-not (has_file($output_fileName))) {
        Write-Host "Downloading jre from $url"
        Get-ChocolateyWebFile 'jre8' $output_fileName $url $url
    }
}

上記の”Get-ChocolateyWebFile”を使うことによって、以下の効果があります。

・ユーザーにダウンロードの進捗をプログレスパーで表示できます。これはChocolateyの標準インタフェースです。
・同一パッケージで32ビット版と64ビット版がある場合、4番目のURLの引数に64ビット版を指定すると環境に応じて自動的にダウンロードするファイルを選択してくれます。

ただし、JRE 8では64ビット環境でも32ビットJREを入れたい、64ビット環境に64ビット、32ビット両方のJREを入れたいという要望がユーザーより複数挙がったために、Chocolateyによるダウンロードの自動選択の機能は使用していません。

同じようにプラットフォームをアドホックに判断していた関数についても標準のAPIを使うよう指摘を受けました。

function use64bit() {
    $is64bitOS = (Get-WmiObject -Class Win32_ComputerSystem).SystemType -match ‘(x64)’
    return $is64bitOS
}

APIの中身を見るとそんなに凄いことしている訳ではないのですが、Chocolateyで提供している機能は徹底して使用して下さいが方針なのでまずはAPIを熟読して自分で作り込む機能を切り分けましょう。

function use64bit($Forcei586 = $false) {
    if ($Forcei586) {
        return $false
    }
    if (Test-Path (Join-Path $script_path "i586.txt")) {
        return $false
    }
    $is64bitOS = Get-ProcessorBits 64
    return $is64bitOS
}

2.3. 個人情報利用の最小化

“Get-ChocolateyWebFile”の使用の指摘には以下も含まれていました。

  • Cookie等個人情報に繋がるものは可能な限り使わなくてすむようにして下さい。

パッケージマネージャーは自動インストールに使われるものなので、特に裏で勝手にCookie等が設定されていたら不信感が募りますよね。故に本当に認証が必要で回避できない場合のみCookie等は使用するようにという方針のようです。

2.4. Chocolatey Update (cup)への対応

これは指摘を受ける前に改善したのですが、新規インストールだけでなく、Chocolatey Update (cup)によるアップデートとアップーデート後のアンインストール(前バージョンに戻す)でも正常に動作するように作っておく必要があります。

JRE 8の場合、パッケージとして別バージョンでもJRE 8のバイナリは一緒の場合があり、その場合新バージョンのバイナリをアンイストールした後、前バージョンのバイナリをアンイストールするとバイナリがなくてこけてしまう問題がありましたので、これまた苦肉の策として、こけた場合の例外を握りつぶしてアンインストールとしては正常に終了するようにしました。

try {
    Uninstall-JRE
} catch {
    # ingore exception
} finally {
    Write-ChocolateySuccess 'jre8'
}

バイナリを扱っている以上仕方ないところもありますが、ここら辺はもうちょっと洗練が必要かなと思います。

2.5. アンインストールのサポート

現在はインストールのみでアンイストールをサポートしていないパッケージが著名なものでも結構あるのですが、今後One-Getの標準リポジトリとして採用されるということを考えると、アンイストールのサポートは必須でしょう。既にJRE 8パッケージではサポートしていますが、Chocolatey APIが揃っているインストールに比べて、アンインストールのAPIは殆ど提供されておらず、以下の説明のように自分で作成するしかありません。

https://github.com/chocolatey/chocolatey/wiki/CommandsUninstall

Known Limitations

  • There are no functions defined in the chocolatey powershell module that would help with uninstall
  • There is no automatic removal of MSIs
  • Uninstall only removes the most current version of a package in the machine repository (instead of giving
  • you options to remove a certain one or all of them)
  • Requires a chocolateyUninstall.ps1 in the package itself, of which many of the currently available packages do not have.

JRE 8パッケージではインストール時にインストールしたバイナリの情報をファイルとして保存しており、その情報からアンイストールするバイナリを決定して、msiexec経由でアンインストールしています。前述したようにアンイストールをサポートしているパッケージは少なく参考にできるコードが少ないのでアンイストールの実装は試行錯誤になります。

function Uninstall-JRE {
    if (Test-Path (Join-Path $script_path "both.txt")) {
        $jre = "/qn /x {26A24AE4-039D-4CA4-87B4-2F864" + $uninstall_id + "F0}"
        Start-ChocolateyProcessAsAdmin $jre 'msiexec'
        $jre = "/qn /x {26A24AE4-039D-4CA4-87B4-2F832" + $uninstall_id + "F0}"
        Start-ChocolateyProcessAsAdmin $jre 'msiexec'
    } else {
        $use64bit = use64bit
        if ($use64bit) {
            $jre = "/qn /x {26A24AE4-039D-4CA4-87B4-2F864" + $uninstall_id + "F0}"
        } else {
            $jre = "/qn /x {26A24AE4-039D-4CA4-87B4-2F832" + $uninstall_id + "F0}"
        }
        Start-ChocolateyProcessAsAdmin $jre 'msiexec'
    }
}
try {
    Uninstall-JRE
} catch {
    # ingore exception
} finally {
    Write-ChocolateySuccess 'jre8'
}

3. 審査基準

JDK 8などダウンロード数が少ないものは再pushしても、審査のプロセスが進みませんでした。審査されるのはある程度のシェアがあるものという前提があるのかもしれません。

4. まとめ

Approvedまでの指摘と審査のプロセスのやりとりはソースコードの隅々までじっくりレビューかつ指摘してくれるというディープなもので、なかなかApprovedにならないなーと辛かったりしましたが、無料でここまでコードを指摘してくれるというのは貴重な機会であります。

Windows 10の発売後はOne-Get側で標準のリポジトリが提供され、APIや審査方法も変わってくるかもしれませんが、しばらくはChocolateyがデファクトのパッケージマネージャーのままなので、Approved Packageを作る際に本記事が参考になれば幸いです。

お次は@kazuakixさんです。

JIRAのスキーマーをastahのクラス図で書いてみる カスタムフィールド編

JIRA Advent Calendar 2011 – Series of JIRA Tipsの12/20(かぶったので2個目)のエントリーです。

JIRAでカスタムフィールドを新規に作る場合、ウィザードを使えば結構簡単に作成できるのですが、既存のカスタムフィールドの属性を変更するときにはカスタムフィールドに関連するスキーマがどのようになっているのか理解が必要です。

で、管理しながらようやく分かってきたらしいカスタムフィールド周りのスキーマが以下です。

単純に言うと、論理的なフィールドの構造と、それをどのような画面に表示するかの2つに分けています。

プロジェクトに適切な「課題タイプ画面スキーム」と「フィールド設定スキーム」を設定すると無事カスタムフィールドが各画面で表示されるようになります。

まだカスタムフィールド周りは何回かしかメンテしたことがないため、理解に誤りがあるかもしれません。

また、外部キー制約、親子関係とかを考えると、UMLよりERで描いた方が分かりやすかったかなと。キー制約が複雑な場合、ERの方が向いていますね。

ngCoreゲームをJasmineで自動テスト

TDDまではいけてませんが、こんなん感じでできます。サーバーサイドやストレージへの依存がある本格的なゲームの場合、なかなか難しいと思いますが、クラスやメソッドレベルを疎結合化しておけば、この積み上げでできそうかなーと今のところ思っています。

1. テストコード

describe('The Yaruo Player Test', function () {
    
    var player;
    
    beforeEach(function () {
        player = new Player(GL2.Root);
    });
    
    it('Creating Player should be success', function () {
        expect(player).toBeDefined();
        
        var position = player.getPosition();
        
        expect(position).toBeDefined();
        
        var x = position.getX();
        var y = position.getY();
        
        expect(x).toEqual(480 / 2);
        expect(y).toEqual(320 / 2);
    });
    
    it('move(480 / 2 + 23, 320 / 2 + 15) should be (480 / 2 + 23, 320 / 2 + 15)', function () {
        player.move(new Core.Vector(480 / 2 + 23, 320 / 2 + 15));
        
        var position = player.getPosition();
        
        expect(position.getX()).toEqual(480 / 2 + 23);
        expect(position.getY()).toEqual(320 / 2 + 15);
    });

    it('move(480 / 2 + 24, 320 / 2 + 16) should be (480 / 2 + 24, 320 / 2 + 16)', function () {
        player.move(new Core.Vector(480 / 2 + 24, 320 / 2 + 16));
        
        var position = player.getPosition();
        
        expect(position.getX()).toEqual(480 / 2 + 24);
        expect(position.getY()).toEqual(320 / 2 + 16);
    });

    it('move(480 / 2 + 25, 320 / 2 + 17) should be (480 / 2 + 24, 320 / 2 + 16)', function () {
        player.move(new Core.Vector(480 / 2 + 25, 320 / 2 + 17));
        
        var position = player.getPosition();
        
        expect(position.getX()).toEqual(480 / 2 + 24);
        expect(position.getY()).toEqual(320 / 2 + 16);
    });

});

2.ソースコード

//Core module provides base functionality
var Core  = require('../NGCore/Client/Core').Core;
// GL2 module provides application graphics
var GL2   = require('../NGCore/Client/GL2').GL2;

exports.Player = Core.MessageListener.subclass(
{
	initialize: function(root) {
        	this._position = new Core.Vector(480 / 2, 320 / 2);
       		this._node = new GL2.Node();

		this._sprite = new GL2.Sprite();
        	this._sprite.setImage('./Content/player.png', [70, 90], [0, 0]);
		this._node.addChild(this._sprite);
    
        	this._node.setPosition(this._position.getX() - 35, this._position.getY() - 45);
                
        	root.addChild(this._node);
	},
    
    move: function(position) {
        if (Math.abs(this._position.getX() - position.getX()) < 24) {
                this._position.setX(position.getX());
        }
        if (this._position.getX()  position.getX()) {
            this._position.setX(this._position.getX() - 24);
        }

        if (Math.abs(this._position.getY() - position.getY()) < 16) {
            this._position.setY(position.getY());
        }
        if (this._position.getY()  position.getY()) {
            this._position.setY(this._position.getY() - 16);
        }
          
        this._node.setPosition(this._position.getX() - 35, this._position.getY() - 45)
    },
    
    getPosition: function() {
        return this._position;
    },
    
    setPosition: function(position) {
        this._position = position;
    },
    
    reset: function() {
        this._position = new Core.Vector(480 / 2, 320 / 2);
        this._node.setPosition(this._position.getX() - 35, this._position.getY() - 45);
    },
});

3. 実行結果

[1] Test "Yaruo Test" started on Tue Dec 13 2011 13:13:41 GMT+0900 (JST)
Device ID: 308B45C0-0A94-5316-8F70-9F7D3DA11E0D

Device capabilities:
	DeviceName: x86_64
	PlatformOS: iPhone OS
	PlatformOSVersion: 4.3.2
	PlatformHW: iPhone Simulator
	PhysicalCpus: 8
	ScreenWidth: 320
	ScreenHeight: 480

[2] Spec Results

"The Yaruo Test Creating Background should be success."
	Started:	Tue Dec 13 2011 13:13:41 GMT+0900 (JST)
	Finished:	Tue Dec 13 2011 13:13:42 GMT+0900 (JST)
	Duration:	0.383s
	Spec passed

"The Yaruo Player Test Creating Player should be success."
	Started:	Tue Dec 13 2011 13:13:42 GMT+0900 (JST)
	Finished:	Tue Dec 13 2011 13:13:42 GMT+0900 (JST)
	Duration:	0.073s
	Spec passed

"The Yaruo Player Test move(480 / 2 + 23, 320 / 2 + 15) should be (480 / 2 + 23, 320 / 2 + 15)."
	Started:	Tue Dec 13 2011 13:13:42 GMT+0900 (JST)
	Finished:	Tue Dec 13 2011 13:13:42 GMT+0900 (JST)
	Duration:	0.007s
	Spec passed

"The Yaruo Player Test move(480 / 2 + 24, 320 / 2 + 16) should be (480 / 2 + 24, 320 / 2 + 16)."
	Started:	Tue Dec 13 2011 13:13:42 GMT+0900 (JST)
	Finished:	Tue Dec 13 2011 13:13:42 GMT+0900 (JST)
	Duration:	0.002s
	Spec passed

"The Yaruo Player Test move(480 / 2 + 25, 320 / 2 + 17) should be (480 / 2 + 24, 320 / 2 + 16)."
	Started:	Tue Dec 13 2011 13:13:42 GMT+0900 (JST)
	Finished:	Tue Dec 13 2011 13:13:42 GMT+0900 (JST)
	Duration:	0.002s
	Spec passed

[3] Test "Yaruo Test" finished at Tue Dec 13 2011 13:13:42 GMT+0900 (JST)
	Test took 0.495s to complete.

5 specs run. 
Assertions:
	12 passed, 0 failed out of 12 total.

JIRAのスキーマーをastahのクラス図で書いてみる ユーザー編

元々微妙にミッションとしてはあったのですが、ひょんなことから最近社内でJIRA管理者もといJIRA大臣として呼ばれることが多いです。

で、JIRAは非常に柔軟なスキーマ構造をしていて、フローにしろ、権限にしろ、項目にしろ、画面にしろなんでもカスタマイズできるのですが、あまりに柔軟すぎて管理者としては最初は何がなんだか分からなかったです。

で、僕はUMLとかERとかダイアグラムを書いて概念を整理するのが好きなので、管理しながら整理した概念を図示化してみることにしました。

アトラシアン謹製のスキーマーはこちら。

Scheme Entity Relations Map

すごく・・・難しそうです。

で、まずは最初の鬼門であるユーザー周りから。

1. ユーザーは特定のプロジェクトに関係なく、グループに属することができます。

2. プロジェクトごとにプロジェクトロールがあり、ユーザーもしくはグループをプロジェクトごとのプロジェクトロールに結びつけます。つまり、プロジェクトごとに直接ユーザーやグループを結びつけるのではなく、プロジェクトロールという概念を使ってワンクッションおきます。これはより柔軟な管理と管理の容易性のバランスをとるために導入されたそうです。プロジェクトに対する各種の権限も基本的にこのプロジェクトロールに結びつけます。この結果、直接ユーザーやグループを権限と結びつけなくてすむようになります。

3. プロジェクトの作成を容易にするためにプロジェクトテンプレートがあります。プロジェクトテンプレートはパワータイプというよりは、JavaScriptのプロトタイプに近いものなので、ステレオタイプ<<prototype>>を使用しました。

4. 同じく毎回プロジェクトロールを個別に設定するのは大変なので、グローバルにプロジェクトロールとグループを結びつけて、各プロジェクトでテンプレートにできるデフォルトプロジェクトロールがあります。同じくプロトタイプに近いものなので、ステレオタイプ<<prototype>>を使用しました。

まとめると、JIRAのプロジェクトのごとのユーザーの各種の権限は基本的にプロジェクトロールを介して設定します。個別にユーザーやグループを結びつけることもできるのですが、管理が煩雑になるため、プロジェクトロールを介するのが標準であり、またベストプラクティスでもあるようです。

まず僕にはこのプロジェクトロールの概念が鬼門だったので、同じようにつまずいた人もいるんじゃないかと思い整理してみました。

アジャイル開発でも信頼度成長曲線は有効か?ツールを使って確かめてみよう

続けてですが、僕も「Software Test & Quality Advent Calendar 2011」の12/2エントリーとして、書きます!

皆さんはテストの完了条件をどのように設定していますか?

多くはテストで発見した障害に対して、重要度もしくは優先度をつけて、

重要度高の残障害 0件
重要度中の残障害 5件

のような条件をクリアーした時に、テスト完了とすることが多いと思います。しかし、これではこれまで発生した障害のみの情報に頼ってテストの完了条件としているため、テスト完了後に発生しうるリスクに対して備えているとは言えません。

それを補完とする手法の一つとして信頼度成長曲線があります。

え、それってメインフレーム時代の大規模プロジェクトとかにしか使えないやつじゃないの?今のアジャイル開発には使ええないよね。俺はTDDで開発しているからバグ出さないぜ、そんなの関係ないよ。

とか言われそうですが、まあ、僕もそう思っていましたが、先ほど障害の重要度や優先度ってそもそも人が付けるものなのでそれ自体主観的でまるで定量的とは言えないので、ちょっとは定量的な根拠がほしいですね。

ということで、最近自分も信頼度成長曲線をなんとか実プロジェクトで使えないかなーと試行錯誤していたのですが・・・そもそもかなり確率統計のバックボーンが必用でかつ自前で一から計算するのはすごく大変だったりします。どの近似曲線を選べばいいのかというのもこれまた経験だけでなく、数学の知識が必要です。

と挫折しかかっていたところ、Excel上で簡単な入力で信頼度成長曲線を描いてくれてかつ、予想総バグ数、予想残存バグ数までを求めてくれるツールを発見しました。

SRATS (Software Reliability Assessment Tool on Spreadsheet Software)

Excelのマクロとして作られています。難し話はさておき、

障害が発見された時間間隔 障害時間
45                1
60                2

のようにテストを開始してから45分後に1件見つかった、その60分後に2件見つかったという風にExcelに記入していき(TracやJIRAのような障害管理ツールからエクスポートすると簡単です)、その時間間隔データを対象にしてSRATSを実行すると、先ほどの予想総バグ数(厳密にいうとバグではなくその結果である障害数)をはじめとする各種の値とグラフが出てきます。

もちろん、あくまでツールは理論に基づいて値を算出しているので、実際のプロジェクトに適用するにはテスト完了基準の一つとしての参考値ぐらいにとどめておくのが無難です。しかし、明らかに障害の発見が収束していない、そのテスト後の障害発生数が高すぎるというリスクを判断する材料には使えます。

注意点として、僕も経験しましたが、通常の障害管理ツールには障害が発見された時間間隔というのは記録されておらず、障害の発見日時もしくは障害票の起票日時のみが記録されていることが多いので、ツールからExcelへエクスポート後、開発の営業時間、テストの実稼働時間を考慮して、障害の発見日時から障害が発見された時間間隔を算出する必要があります。僕はここをマクロで自動化しようと思いましたが、休日出勤とか毎日のテストの実稼働時間とかが異なることまで考慮すると自動化がちょっと困難だったので、エクスポート後手動で算出しています。あんまり多くの障害票がある場合、やはり自動化したほうがよいかもしれません。

で、肝心のアジャイル開発のような短期間開発のスプリントやイテレーションにも使えるのかという話ですが、それは皆さんで試してのお楽しみです。というのも、あまりに乱暴なので、ひそかに障害票からエクスポートして検証してみた感じでは思ったより使えます。特にリリース後に発見された障害まできちんとトラッキングしていくとどれだけ曲線の精度があるのかが経験値としてチームや組織に定着していくようになると思うので、まずはひそかに計測してみるというのをやってみるとよいかなと思います。

読了:ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門

http://amzn.to/qCgfPW

構造化脳の読者向けにやさしくHaskellを解説ということで、まあ、構造化脳というか、情報数学苦手な(OTL、情報学科出身なのに><)僕でもなんとか理解できそうなに書かれています。まあ、中盤ぐらいまでは僕でも理解できたけど、やっぱり、Monadしんどいです。実はこれ超入門なので、ラムダ計算の理論とかきっちり踏まえた上でないと部品を使うことぐらいはできても、ライブラリ(Haskellではモジュール)を作ることはできないかなと思いました。

最終的にはCoqのような証明記述言語にたどり着きたいところですが、まずは本書をはじめとして、比較的初心者にもやさしい入門本で写経を繰り返してというところからちまちま進むというのが無難かなーと思います。読んでいて、かなりしんどかった群論の単位とか思い出しちゃった。その他、チューリングマシン、ラムダ計算とかも1コマづつありましたが、今ようやく多少理解できはじめてますwはい。

読了:入門OCaml ~プログラミング基礎と実践理解~

http://amzn.to/n0BPkP

関数型言語マスターの従兄に関数型言語勉強したいと相談したら、Haskellの前にまずOCamlさらっとやっとけとのことだったので、まずはこちらをば。最後のほう微妙にしんどかったですけど、入門本としてまあまあかなあ。OCamlは遅延評価がデフォルトじゃないのでその分Haskellよりわかりやすいとのことでしたが、式の展開とかをHaskellで学ぶとあっちからでもいいかなとも思ったり。

Amazonの書評では以下のほうがオススメらしいので、こちらも買ってみようかと思います。

プログラミング in OCaml ~関数型プログラミングの基礎からGUI構築まで~

http://amzn.to/nSte8Z