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

InnoSetupで作成したインストーラのバージョンについて

issで定義した値がどこに反映されるのか、いまいち分かりにくいのでメモ。

ミニマムな構成でこんな感じに定義したとして、


AppVersionはインストーラの初期画面と、アプリケーションの追加と削除に反映される。


ちなみに AppVerName を指定するとそちらが優先される。


ファイルのプロパティで表示される「詳細」タブに表示されるのは VersionInfoVersion(ファイルバージョン) と VersionInfoProductVersion(製品バージョン)。

経路列挙モデルで階層構造

参考URL: SQLで木と階層構造のデータを扱う(2)―― 経路列挙モデル
ちょっと仕事で階層構造を持つテーブルをリファクタリングしたくなったので、いろいろ調べてみたところ経路列挙モデルが便利そうなので試してみた。試してみたDBはSQL Server 2008 R2。

まずテーブルはこれ。区切り文字は「/」にした。

;CREATE
CREATE TABLE NODES (
	ID int PRIMARY KEY
	, PATH NVARCHAR(256) NOT NULL
	, UNIQUE(PATH)
	, CONSTRAINT PATH_NAME_CHECK CHECK(RIGHT(PATH,LEN(STR(ID, 1))+1) = (STR(ID, 1)+'/'))
);

IDでPATHを表現しているが、intなので区切り文字を含ませない制約はつけてない。その代わり、PATHの最後に自分のIDを含むような制約をつけるようにした。


経路列挙モデルで分かりにくいのは、IDをどうやって決めればいいのかってことだった。使ってないIDを自分で把握して、というのは現実的ではない。なのでINSERT時に最大値+1を計算して代入することにした。親がいない時と親がいる時でSQLを分けるのも分かりにくいので、CASEを使って頑張ってみた結果がこれ。2ヶ所のPARENT_IDをアプリから設定してあげればOK。

;INSERT
INSERT INTO NODES (
	ID
	, PATH
) select
	(CASE
		WHEN COUNT(ID)=0 THEN 1
		ELSE MAX(ID)+1
	END) AS ID2
	, (CASE
		WHEN (SELECT COUNT(*) FROM NODES WHERE ID=/*PARENT_ID*/1) > 0 THEN
		(SELECT PATH FROM NODES WHERE ID=/*PARENT_ID*/1)
 		ELSE '/'
 	END) + CONVERT(nvarchar, CASE WHEN COUNT(ID)=0 THEN 1 ELSE MAX(ID)+1 END) + '/' AS PATH2
FROM NODES


SELECTは参考URLをSQL Server向けに修正しただけ。

;ルートを求める
SELECT
	*
FROM
	NODES
WHERE
	ID = REPLACE(PATH,'/','')
;リーフを求める
SELECT
	*
FROM
	NODES P
WHERE NOT EXISTS (
	SELECT
		*
	FROM
		NODES C
	WHERE
		C.PATH LIKE P.PATH + '_%');
;ノードの深さ
SELECT
	ID
	, PATH
	, LEN(PATH) - LEN(REPLACE(PATH, '/', '')) -1 AS LEVEL
FROM
	NODES
;木の高さを求める
SELECT
	MAX(LEN(PATH) - LEN(REPLACE(PATH, '/', '')) -1) AS HEIGHT
FROM
	NODES;
;階層をインデントで表現する
SELECT
	ID
	, PATH
	, REPLICATE(' ', LEN(PATH) - LEN(REPLACE(PATH, '/', '')) - 2) + STR(ID, 1)
FROM
	NODES
;親から見た場合の子供
SELECT
	P.ID AS PARENT_ID
	, C.ID AS CHILD_ID
	, C.PATH AS CHILD_PATH
FROM
	NODES P
LEFT OUTER JOIN
	NODES C
ON
	P.PATH = (SELECT MAX(PATH) FROM NODES WHERE C.PATH LIKE PATH + '_%');
;子から見た場合の親
SELECT
	CHILD.ID AS CHILD_ID
	, PARENT.ID AS PARENT_ID
	, CHILD.PATH AS CHILD_PATH
FROM
	NODES CHILD
LEFT OUTER JOIN
	NODES PARENT
ON
	CHILD.PATH > PARENT.PATH
    AND PARENT.PATH = (SELECT MAX(PATH) FROM NODES WHERE CHILD.PATH LIKE PATH + '_%');

パスを列挙する(列持ちバージョン)はどちらもSQL Serverでは意図した結果にならなかった。特に使う予定もないので割愛。


次はデータの削除。今回の場合自分の配下も一緒に削除されて問題ないので、こんな感じでDELETE_IDを指定してDELETEする。

;部分木の削除:一般化
DELETE FROM
	NODES
WHERE
	PATH LIKE (SELECT PATH FROM NODES
                   WHERE ID = /*DELETE_ID*/4) + '%';


次にUPDATE。間に割りこむことはしないが、部分木の移動はあるので考えてみた結果がこれ。部分木の先頭ROOT_IDと新しい部分木の親NEW_ROOT_IDを指定してUPDATEする。一応部分木を独立させる場合にも対応してるはず。

UPDATE NODES
SET
	PATH = REPLACE(PATH,
		(SELECT PATH FROM NODES WHERE ID=/*ROOT_ID*/7),
		(CASE
			WHEN (SELECT COUNT(ID) FROM NODES WHERE ID=/*NEW_ROOT_ID*/null)=0 THEN '/'
			ELSE (SELECT PATH FROM NODES WHERE ID=/*NEW_ROOT_ID*/null) END)
		 + STR((SELECT ID FROM NODES WHERE ID=/*ROOT_ID*/7), 1) + '/')
WHERE
	PATH LIKE (SELECT PATH FROM NODES WHERE ID=/*ROOT_ID*/7) + '%'


なんとなく必要なSQLは書けたので、既存DBを使ったDAOのテストでも書いて、それからテーブル変更かなー。
それにしても、そろそろSQLは一回SELECTしたデータを使いまわせるようにしてくれないかな。同じものをいろんなとこにコピペするのやだわー。

これの続き。
参考URL : http://mercurial.selenic.com/wiki/Subrepository

Mercurial 2.0 ( TortoiseHg 2.2 ) で仕様が変わったらしく、以前の方法だとうまくいかない。

[subpaths]
subrepo_name = https://bitbucket.org/toruuetani/virtualenv-scaffold

たとえばメインリポジトリのクローン元が https://bitbucket.org/toruuetani/hoge の場合、以下のようにする。

[subpaths]
https://bitbucket.org/toruuetani/hoge/subrepo_name = https://bitbucket.org/toruuetani/virtualenv-scaffold


ちなみに これで作ったバッチは以下のようになる。

@ECHO OFF
SET ROOT_DIR=%~DP0
SET TOP_REPO=PATH_TO_TOP
SET SECOND_REPO=PATH_TO_SECOND
SET THIRD_REPO=PATH_TO_THIRD


SET TOP_HGRC=%ROOT_DIR%\.hg\hgrc
SET SECOND_HGRC=%ROOT_DIR%\SECOND_DIR\.hg\hgrc


:CREATE_TOP
hg init
ECHO [paths]>%TOP_HGRC%
ECHO default = %TOP_REPO%>>%TOP_HGRC%
ECHO;>>%TOP_HGRC%
ECHO [subpaths]>>%TOP_HGRC%
ECHO %TOP_REPO%/subrepo_second = %SECOND_REPO%>>%TOP_HGRC%


:CREATE_SECOND
IF NOT EXIST %ROOT_DIR%SECOND_DIR MKDIR %ROOT_DIR%SECOND_DIR
CD /D %ROOT_DIR%SECOND_DIR
hg init
ECHO [paths]>%SECOND_HGRC%
ECHO default = %SECOND_REPO%>>%SECOND_HGRC%
ECHO;>>%SECOND_HGRC%
ECHO [subpaths]>>%SECOND_HGRC%
ECHO %SECOND_REPO%/subrepo_third = %THIRD_REPO%>>%SECOND_HGRC%


:UPDATE
CD /D %ROOT_DIR%
hg pull
hg update -C


:END
PAUSE

py2exeでハマったことの解決法

環境は Windows + Python 2.7.2 + virtualenv 1.7

UAC で管理者権限を要求する

# -*- encoding: utf-8 -*-
from distutils.core import setup
import py2exe #python setup.py py2exe するために必要

setup(
    console = [{'script' : 'hoge.py', 'uac_info' : "requireAdministrator",}],
)

実行時発生する ImportError を回避する

via http://stackoverflow.com/questions/1979486/py2exe-win32api-pyc-importerror-dll-load-failed

# -*- encoding: utf-8 -*-
from distutils.core import setup
import py2exe #python setup.py py2exe するために必要

py2exe_options = {
    'dll_excludes': [ "mswsock.dll", "powrprof.dll" ],
}

setup(
    options = {'py2exe': py2exe_options},
)

VC2008 ランタイムの問題を回避する

Python 2.6 からは VC2008 でコンパイルされているため、 VC2008 ランタイムの問題が発生する。特に問題なのは、ほとんどの環境では SP1 までアップデートされるため、バージョンが 9.0.30729.XX になること。Python が要求するのは 9.0.21022.8 であり、このバージョンのランタイムがないと exe が起動できない。基本的に Windows Update すれば問題はなくなるはずだが、OSのクリーンインストール直後などでも動作できるようにしたい。開発マシンの C:\Windows\WinSxS には該当のDLLが存在するはずなので、これを集めてくるようにすれば問題ないはず。

# -*- encoding: utf-8 -*-
from distutils.core import setup
from glob import glob
import os
import py2exe #python setup.py py2exe するために必要

candidates = glob(r"C:\Windows\winsxs\Manifests\x86_microsoft.vc90.crt_*_9.0.21022.8_*.manifest")
if not candidates:
    raise Exception("Could not find VC9 runtime( 9.0.21022.8 ) from 'C:\Windows\winsxs'.")

dirname = os.path.splitext(os.path.basename(candidates[0]))[0]
data_files = [("Microsoft.VC90.CRT", [candidates[0]] + glob(os.path.join(r"C:\Windows\winsxs", dirname, "*.*")))]

setup(
    data_files=data_files,
)

2012/01/14 追記
上記のままだとmanifest名が正しくないため、名前をリネームする必要がある。

from glob import glob
import os
import shutil
build_dir = "/path/to/build_dir"
manifests = glob(os.path.join(build_dir, r"Microsoft.VC90.CRT", "*.manifest"))
shutil.move(manifests[0], os.path.join(build_dir, "Microsoft.VC90.CRT", "Microsoft.VC90.CRT.manifest"))

TortoiseHg 2.x 入門記事にMercurialコマンドリファレンス追記

最近書いてる TortoiseHg 入門記事に、Mercurialのコマンドリファレンスを追記した。
Windows+TortoiseHg 2.x で始めるMercurial

今回改めて Mercurial コマンドのヘルプを調べてみたけど、ほとんどオプションは使ってない。このあたりが Git との違いなんだろうな。

Sphinxドキュメントにソーシャルボタンを設置して、bitbucketに公開してみた

結果はこれ → http://toruuetani.bitbucket.org
リポジトリはこれ → https://bitbucket.org/toruuetani/toruuetani.bitbucket.org

ソーシャルボタンの設置

参考URL

基本的には参考URLの通りにやれば問題ない。ただし、このままだと確認用にビルドしたドキュメントでもソーシャルボタンが設置され、表示に失敗する。それだけならまだいいが、ドキュメント上部に表示すると失敗するまで本来のドキュメントまで表示されない。なので確認用ビルド時と公開用ビルド時にソーシャルボタンの設置・非設置を切り替えられるようにしてみた。

まずはSphinxドキュメントが $/source にある場合、 $/source/_templates/layout.html を下記の内容で追加する。
はてなとfacebookについては、「http://toruuetani.bitbucket.org/」の部分を公開URLにあわせて修正すること。

{% extends "!layout.html" %}


{%- block document %}
    <div class="documentwrapper">
    {%- if render_sidebar %}
      <div class="bodywrapper">
    {%- endif %}
        <div class="body">
          {% if need_social_buttons %}
          <div style="height: 30px;">
          <table cellpadding="5px">
          <tbody>
          <tr><td>
          <!-- hatenaコード -->
          <a href="http://b.hatena.ne.jp/entry/http://toruuetani.bitbucket.org/{{ pagename }}{{ file_suffix }}"
             class="hatena-bookmark-button"
             data-hatena-bookmark-title="{{ title }}"
             data-hatena-bookmark-layout="standard"
             title="このエントリーをはてなブックマークに追加">
          <img src="http://b.st-hatena.com/images/entry-button/button-only.gif" alt="このエントリーをはてなブックマークに追加" width="20" height="20" style="border: none;" />
          </a><script type="text/javascript" src="http://b.st-hatena.com/js/bookmark_button.js" charset="utf-8" async="async"></script>
          </td><td>
          <!-- Twitterコード -->
          <a href="https://twitter.com/share"
             class="twitter-share-button"
             data-count="horizontal">Tweet</a>
          <script type="text/javascript" src="//platform.twitter.com/widgets.js"></script>
          </td><td>
          <!-- google+1 headerタグ用コード -->
          <script type="text/javascript" src="https://apis.google.com/js/plusone.js">
          {lang: 'ja'}
          </script>
          <!-- google+1ボタン表示用コード -->
          <g:plusone></g:plusone>
          </td><td>
          <!-- facebookコード -->
          <div id="fb-root"></div>
          <script>(function(d, s, id) {
            var js, fjs = d.getElementsByTagName(s)[0];
            if (d.getElementById(id)) {return;}
            js = d.createElement(s); js.id = id;
            js.src = "//connect.facebook.net/ja_JP/all.js#xfbml=1";
            fjs.parentNode.insertBefore(js, fjs);
          }(document, 'script', 'facebook-jssdk'));</script>
          <div class="fb-like"
               data-href="http://b.hatena.ne.jp/entry/http://toruuetani.bitbucket.org/{{ pagename }}{{ file_suffix }}"
               data-send="false"
               data-layout="button_count"
               data-width="450"
               data-show-faces="true"></div>
          </td></tr></tbody></table>
          </div>
          {% endif %}
          {% block body %} {% endblock %}
        </div>
    {%- if render_sidebar %}
      </div>
    {%- endif %}
    </div>
{%- endblock %}

本来なら body ブロックだけ用意すればいけるかと思ったが、ドキュメント側で body ブロックを完全にオーバーライドしてしまうらしい。なので Sphinx のデフォルトテンプレートから layout.html をコピーしてきて、ソーシャルボタン設置コードを追記した。さらにそのコードを if ブロックで切り替えられるようにして、その切り替えは、make.batにオプションを追記することで可能にした。
ポイントは %SPHINXBUILD% (sphinx-build) に渡すパラメータ「-A need_social_buttons=1」。「-A name=value」の形式で渡すと、Sphinxテンプレート内の変数をオーバーライドできる。boolean型の場合は0または1を渡せばいい。

REM ソーシャルボタン有効化オプション
if "%1" == "html-publish" (
	%SPHINXBUILD% -b html -A need_social_buttons=1 %ALLSPHINXOPTS% %BUILDDIR%/html
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished. The HTML pages are in %BUILDDIR%/html.
	goto end
)

こうすると、いつものように

make.bat html

するとソーシャルボタンを設置しないHTMLが生成されて、

make.bat html-publish

するとソーシャルボタンを設置するHTMLが生成される。

Sphinxドキュメントの公開

参考URL

次は作成したHTMLの公開。せっかく bitbucket のアカウントを持ってるので、 Sphinx ドキュメントの公開先に使ってみた。ほぼ参考URLの通りで、作成したHTMLもリポジトリに登録するのが注意点。

作成するリポジトリ名は、bitbucketのアカウント名.bitbucket.org とする。あとは通常のリポジトリと同じように使えばいい。

Sphinxでドキュメントをビルドすると、指定したビルドディレクトリ配下にターゲットに応じたディレクトリが作られて、その下に生成したドキュメントが配置される。HTMLを指定した場合は

ビルドディレクトリ/html/index.html

といった感じ。これを毎回リポジトリのルートディレクトリまでコピーするのも面倒なので、ビルドしたらルートディレクトリにHTMLをコピーするバッチファイルを用意した。DOC_DIRを各環境にあわせて変更すればそのまま使えるはず。あとは生成したHTMLをコミットして、bitbucketにpushすれば公開される。

@ECHO OFF
SET ROOT_DIR=%~DP0
SET DOC_DIR=%ROOT_DIR%\doc
CD /d %ROOT_DIR%

PUSHD %DOC_DIR%
IF "%1"=="publish" (
    CALL make.bat html-publish
) ELSE (
    CALL make.bat html
)
POPD
IF EXIST %DOC_DIR%\build\html\index.html (
    XCOPY /S /Q /Y %DOC_DIR%\build\html\* %ROOT_DIR%
)

TortoiseHg 2.x 入門記事にMercurial採用理由追記

以前書いてた TortoiseHg 入門記事を Ver.2.2 ベースで書きなおした記事に、なぜMercurialを採用するのか追記した。
Windows+TortoiseHg 2.x で始めるMercurial

ある程度分量があってはてなでは書きにくいことと、最近ドキュメントはSphinxで書いたほうが書きやすいので、すべてSphinxで書きなおした。
このあとはブランチの使い方に言及していく予定。果たしていつになるのか・・・