デモ

Architect SeminarでGuidance Automationのデモを行いました。今回は、Guidance Packageの利用と作成という内容でした。
ただ、日程の都合上タイトルが早めに決まってしまったため、内容と少し乖離していたのが問題で、受講者層もちょっとそちらに寄っていたような感じがありました。

また、受講者の方々のレベルや前提知識がそれぞれ異なるため、デモは難しいです。とはいえ、今回のセミナーがGuidance Automationを検討するきっかけにでもなれば幸いです。

GAXに不足しているアセンブリ

デモ用にインストーラを作っていて初めてわかったんですが、GAXではMicrosoft.Practices.RecipeFramework.Library.dllがインストールされないようです。GAXとGuidance Packageのみをインストールすると、Guidance Packageの利用時に例外が起きます。

で、guidanceautomation.netとかを見てみると、インストーラに含めろとのことなので、やってみたらうまくいきました。まあ仕方ないか。回避できるし。

しかしVPCは起動が遅いですね。うちのマシンが遅いだけなのか。

VSTemplateのNameとDescription

Guidance Automationで使用するSolutionやItemのVSTemplateだが、普通に作成するとNameとDescriptionに日本語を使用した場合に文字化けしてしまう。

これについて調べてみた。

  1. Register時にbin/Debug/Templates/Items.Cache内に.vsdirファイルを作成する。このとき、UTF-8で書かれているようだ。
  2. 実行時にはこれを読み込んで表示する。このとき、S-JISで読んでいるようだ。

とりあえず、Register後にvsdirファイルをS-JISで保存しなおしたら動いた。
ちなみに、VSTemplateファイルのエンコードを変えてみても、vsdirファイルはUTF-8で作成されることまでは調べがついている。

初期化を行うコードの文字列表現を生成する

諸事情から、与えられた値に初期化するコードの文字列を生成することになった。たとえば、System.Drawing.Colorの場合、値は次の4通りの方法で初期化される。

  1. System.Drawing.Color.Empty
  2. System.Drawing.SystemColors.*
  3. System.Drawing.Color.*
  4. System.Drawing.Color.FromArgb

この場合、2番目のものを除けば、System.Drawing.Color.FromArgbで生成できるものの、SystemColorsのものはどうしようもない。さらに、特定の型なら決め打ちすればいいのだが、他の型の場合も考えるとその方法は取れない。

で、色々と調べていたところ、System.ComponentModel.Design.Serialization.InstanceDescriptorクラスに変換するとこの手の初期化処理をやってくれることがわかった。
型に依存しないように注意すると、

object value; // 対象の値

TypeConverter converter = TypeDescriptor.GetConverter(value);
InstanceDescriptor descriptor = (InstanceDescriptor)converter.ConvertTo(
  value, typeof(InstanceDescriptor));

次の問題は、このInstanceDescriptorをどのように文字列に直すかだ。文字列に直すというと、System.CodeDom.Compiler.CodeDomProviderだ。

CodeExpression expression // 対象の式
using (StringWriter writer = new StringWriter())
{
  CSharpCodeProvider provider = new CSharpCodeProvider();
  provider.GenerateCodeFromExpression(expression, writer,
    new CodeGeneratorOptions());
  return writer.GetStringBuilder().ToString();
}

これで文字列表現が得られた。

さて、残った部分は、InstanceDescriptorからCodeExpressionを生成する部分だ。ここがなかなかわからなかった。それらしいクラスも見つからなかったので、自分で作ってみた。
初期化に必要そうな式しか実装していないので、式解釈としては完全ではない。これで対応できなかったものがあったら考える。

/// <summary>
/// 初期化式を生成する
/// </summary>
/// <param name="argumentValue">初期化値</param>
/// <returns>初期化式</returns>
private CodeExpression CreateCodeExpression(object value)
{
  if (value == null)
    return new CodePrimitiveExpression();

  // InstanceDescriptorを生成し、そこからコードを生成する
  TypeConverter converter = TypeDescriptor.GetConverter(value);
  InstanceDescriptor descriptor = (InstanceDescriptor)converter.ConvertTo(
    value, typeof(InstanceDescriptor));

  CodeExpression[] parameters;
  switch (descriptor.MemberInfo.MemberType)
  {
    case MemberTypes.Constructor:
      parameters = GetParameters(descriptor.Arguments);
      return new CodeObjectCreateExpression(
        descriptor.MemberInfo.DeclaringType,
        parameters);
    case MemberTypes.Field:
      return new CodeFieldReferenceExpression(
        new CodeTypeReferenceExpression(descriptor.MemberInfo.DeclaringType),
        descriptor.MemberInfo.Name);
    case MemberTypes.Property:
      return new CodePropertyReferenceExpression(
        new CodeTypeReferenceExpression(descriptor.MemberInfo.DeclaringType),
        descriptor.MemberInfo.Name);
    case MemberTypes.Method:
      parameters = GetParameters(descriptor.Arguments);
      return new CodeMethodInvokeExpression(
        new CodeTypeReferenceExpression(descriptor.MemberInfo.DeclaringType),
        descriptor.MemberInfo.Name,
        parameters);
    default:
      throw new Exception(string.Format("Unknown MemberType: {0}",
        descriptor.MemberInfo.MemberType));
  }
}

private CodeExpression[] GetParameters(ICollection arguments)
{
  CodeExpression[] result = new CodeExpression[arguments.Count];
  int i = 0;
  foreach (object argument in arguments)
  {
    CodeExpression expression;
    if (argument == null)
      expression = new CodePrimitiveExpression(null);
    else if (argument is string || argument.GetType().IsPrimitive)
      // string は IsPrimitive = false だが Primitive式
      expression = new CodePrimitiveExpression(argument);
    else
      expression = CreateCodeExpression(argument);
    result[i++] = expression;
  }
  return result;
}

T4 Editor

http://weblogs.asp.net/gmilano/archive/2006/03/16/440356.aspx

Guidance Automationで(DSL Toolsでも)用いるT4 Templateは、Visual Studio上での編集時にテキストの色づけがされていなかったので見づらいという問題がありました。
このT4 Editorを使用することにより、ASPXファイルのようにスクリプトレット部分の色づけがされて、とても見やすくなります。

VSTemplateを使う際

例によって全然情報がないので全てTry&ErrorでやらないとならないGuidance Automationですが、今回の問題はVSTemplateを展開する際にRecipeのArgumentが置換されないという問題。
SC-SFのGuidance Packageではできているのになぁ…と思いつつ色々と調べていたところ、Guidance PackageでのVSTemplateはRecipe起動ではなく、VSTemplateからRecipeを起動していることが判明。
すなわち、

  • Recipe→VSTemplate: Argumentが渡されない
  • VSTemplate→Recipe: Argumentが渡される
  • (そしたらと思って試した)Recipe→VSTempalte→Recipe: エラー

となるようです。

ただ、後者だと必ずVSTemplate展開の際のダイアログ(プロジェクトの追加)が表示されたり、そこで入力した位置に展開されたりとかの制約があります。
結構困ったなぁ。