2014年5月30日金曜日

Jenkins で VSS プラグイン を使う

こんにちは。yu1row です。

ソースコード管理に VSS を使用しているプロジェクトを Jenkins で CI しようとすると、「Visual SourceSafe Plugin」ってのがありまして、これを使用することにしたんです。
紆余曲折ありまして、なんとか使用できるようになったんで備忘録として書き留めておきます。
そうそう、当該の「VSS を使用しているプロジェクト」の VSS のバージョン、6.0d なんです。
2005 でもないんです。こんなプロジェクト、他の会社とかでもまだまだあるんですかね?

前提条件

  • Jenkins サーバマシン (Windows) には VSS のインストールが必要
  • たぶん Linux では動かない(※com4j ってので COM に動的バインドしてるみたい)
  • VSS のデータベース初期化ファイル (srcsafe.ini) は Windows 共有フォルダにアクセスしますので、このファイルにアクセス権がないと Jenkins のビルド時にソースを落としてくることができません
    • Jenkins のプロセスを Windows のサービスとして起動している場合は、起動するユーザを共有フォルダにアクセスできるものに変更しておきます
      (たぶん初期状態では起動するユーザは「Local System」になっていると思います)
    • Jenkins をサービスとして起動していない場合は、プロセス起動時のユーザが共有フォルダにアクセスできるようにしておきます

      ※ユーザID/パスワードが共有フォルダへのアクセスに使用するものとまったく同じものを作っておくか、「Windows資格情報コンテナー」をいじってアクセスするID/パスワードを適切なものにしておきます


VSSへのチェックインをしていないのに「変更あり」とされ続けてしまう問題

「SCM をポーリング」にチェックを入れて設定していたら、ソースを変更していないのにポーリングの時間毎に毎回以下のような「変更あり」の状態になってしまい、ビルドがいっぱい走っていました。
Started on 201X/XX/XX 10:00:00
Polling SCM changes on master
[poll] No previous build, so forcing an initial build.
Done. Took 0 ms
Changes found

なんでやろー?って思ってたら、VSS プラグインのバグのようです。
issues に挙がってました。


しかし VSS プラグインはしばらく更新されていませんし、自分でパッチ当ててビルドするしかなさそうです。

自前でパッチ当て&ビルドする


プラグインを開発するための手順ページとして以下を参考にしました。
[Jenkins プラグインを開発する] http://qiita.com/kazuqqfp/items/ded99eb8d7bd967b9d2a

  1. 上記ページの1の手順、maven をインストールします
    ※maven のインストールは以下の URL でアーカイブをダウンロードして「Windows 2000/XP」の所よく読んでインストールして下さい。
    http://maven.apache.org/download.cgi
  2. 2~4の手順の代わりに、上記の VSS プラグインのソースを落としてきて、さらにパッチを当てます
    ※1行だけ、if 文の「==」を「!=」に変更するだけなんで、手で修正してもいいですけどね
  3. 5の手順どおり、ソースのルートフォルダで「mvn install」します
  4. hpi ファイルが完成したら Jenkins に手動でインストールします
何度も何度もビルドされちゃう問題はこれで回避できました。
これで幸せになれる人がいれば嬉しいです。

2014年5月29日木曜日

Visual Studio、MSBuild での Clean で特定フォルダを削除する

こんにちは。yu1row です。

タイトルの通りです。備忘録として残します。
ビルドの際 Clean で特定のフォルダを削除する方法です。

*.csproj や *.vbproj を直接開いて、<Target> タグに <RemoveDir> を追加して、削除するフォルダを記述しておきます。

以下記述例
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
...
  <Target Name="Clean" Condition=" '$(Configuration)' == 'Release' ">
    <RemoveDir Directories="$(TargetDir)" />
  </Target>
...
</Project>

  • 上記の例では Release の時だけ出力先フォルダが削除されるが、"Condition" を書かなければ Release のときも Debug の時も削除される
  • Directories の削除対象フォルダは";"で区切れば複数指定可能。
  • .vshost.exe や .pdb が邪魔して削除できない場合があるが、その場合は以下を参照
    .vshost.exeファイルと.pdbファイルが生成されないようにするには?[VS 2008、VS 2005]
    http://www.atmarkit.co.jp/fdotnet/dotnettips/831stoppdbfile/stoppdbfile.html
以上であります。

2014年5月28日水曜日

GetWindowText のデッドロック

こんにちは。yu1rowです。

GetWindowText(SendMessage で WM_GETTEXT を使用した場合も同じ) でハマったので、その情報を備忘録として残します。
※回避コードだけ見るなら、一番下までスクロール。

マルチスレッドで EnumWindows で列挙した全 Window の文字列を GetWindowText やSendMessage を使って調べる処理を DLL に記述。
そしてこの DLL の関数を Excel VBA から呼び出すというケースで、スレッドがブロックしてしまっていつまでも終了しないことがある。

SendMessage を MSDN で確認すると、以下のように記述されていた。

複数のスレッド間で送信されたメッセージが処理されるのは、受信側スレッドがメッセージ取得コードを実行したときだけです。送信側スレッドは、受信側スレッドがメッセージの処理を終えるまで、ブロックされます(待機状態になります)。

しかし、日本語版に無い情報を英語版の MSDN で発見。
To prevent this, use SendMessageTimeout with SMTO_BLOCK set.For more information on nonqueued messages, see Nonqueued Messages.
MSDN に限ったことではないけれど、API リファレンスの英語版には日本語訳では見つけられない重要な文章がサラッと書かれている場合が見受けられる。英語版もたまには読んでみたほうがいい。

全ウィンドウの文字列を GetWindowText(SendMessage) で取得する処理を別スレッドで起動・終了待ちをする処理をExcel VBA で行うときに問題が発生した。
その別スレッドから Excel のウインドウ自体にも SendMessage することになるんだけど、そうするとずっと返ってこない。ブロックしちゃうってことらしい。

SendMessage でブロックされていてスレッドが終了しない場合、SendMessage の代わりにSendMessageTimeout を使用して、適切なタイムアウト時間を指定すれば良いとのこと。
以下コード例。
SendMessageTimeout(hWnd, WM_GETTEXT, 1024, (LPARAM)text, SMTO_BLOCK, 100, &dwRet);
  • 上記 100 となっている値は適切なタイムアウト時間を設定すること
  • SMTO_NORMAL、SMTO_BLOCK|、MTO_ABORTIFHUNG を必要に応じて組み合わせて使用すると良いと思う(オプションの詳細は MSDN 見てね)

ただし上記を使用すると、スレッド終了後の待機(WaitForSingleObject とか)でなんかちょっとだけ待機時間が発生して遅くなる。
上記の例であれば、「100msec×スレッド数」ぐらい?

これが気になる場合、「呼出元プロセスのウインドウに対して SendMessage するから止まる」 というデッドロックが元々の原因であるとすれば、上記の SendMessageTimeout を使用する前に、GetWindowThreadProcessId と GetCurrentProcessId を使用して呼出元プロセスのウインドウを回避するチェックを組み合わせるともっと良いかもしれない。
こうすると、ぅぉっなんか遅いってならない。ピュッピュッって終了してくれる。

以下上記を実装した例。呼出元プロセスのウィンドウは回避、ブロックしても 100ms で終わるようにしている。
DWORD pID;
GetWindowThreadProcessId(hWnd, &pID);
if (GetCurrentProcessId() == pID)
{
    return 0;
}

DWORD dwRet;
if (SendMessageTimeout(hWnd, WM_GETTEXT, 1024, (LPARAM)text, SMTO_BLOCK, 100, &dwRet) == 0)
{
    return 0;
}

ハマった。こんときゃ辛かった。

2014年5月27日火曜日

C# の配列変換

こんにちは。yu1row です。

配列変換の方法を備忘録として書いておきます。
int[] arr みたいな配列があったとして、これから string[] を得る場合、どしたらいいんでしょうか?

当然、これはダメです。
int[] arr = new int[] { 1, 2, 3 };
string[] dest = (string[])arr; // Can't compile

以下のように単純にループで回せば、まぁいけますよね。
int[] arr = new int[] { 1, 2, 3 };
string[] dest = new string[arr.Length];
for (int i = 0; i < arr.Length; i++)
{
    dest[i] = arr[i].ToString();
} 

Array.CovertAll() を使えばなんかすっきりした感じになる気がします。
int[] arr = new int[] { 1, 2, 3 };
string[] dest = Array.ConvertAll(arr, delegate(int i) { return i.ToString(); });

ラムダ式が使える(C# 3.0以降)のであれば、もっとすっきりした感じになる気がします。
int[] arr = new int[] { 1, 2, 3 };
string[] dest = Array.ConvertAll(arr, i => i.ToString());

これだけです。 もっとカッコイイくて短くてジェネリックで、特殊条件にも対応できるコードにできる方法があれば、誰か教えてください。

2014年5月26日月曜日

なんてステキなString.Join()

こんにちは。yu1rowです。

区切り文字を使った文字列連結に便利な String.Join() のご紹介です。
C# に限らず、区切り文字を使った文字列の連結はちょっと面倒に感じることがあります。
たとえば、以下の例のような{"A","B","C"}のような配列を"A - B - C"みたいに繋げたいとき、どうしますか?

入力配列出力値
{"A","B","C"}"A - B - C"
{"A"}"A"
{}""

.NET では、String に String.Join(String, String[]) というメソッドがありまして、これで上記を実現してくれます。

コード例
string[] arr = new string[]{ "A", "B", "C" };
Console.WriteLine(String.Join(" - ", arr)); // Output: "A - B - C"

便利。使わない理由はないと思う。

ただ、これ入力が String の配列なんで、int 型とか、他の型の配列はいちいち変換しなきゃいけない。
ただし、.NET Framework 4 以降は String.Join(String, Object[]) なんてオーバーロードが追加されまして、これが便利なんですわ。

一応コード例
int[] arr = new int[] { 1, 2, 3 };

// For C# 2.0
Console.WriteLine(String.Join(" - ", Array.ConvertAll(arr, delegate(int n) { return n.ToString(); })));

// For C# 3.0 or later
Console.WriteLine(String.Join(" - ", Array.ConvertAll(arr, n => n.ToString())));

// For .NET Framework 4.0 or later
Console.WriteLine(String.Join(" - ", arr));


使わんテはありまへんな! 今日のネタはこれだけ。それでは!