NAntでClickOnceの発行を自動化する 2

前回
やっとこさMSBuildからClickOnceの発行をする方法がわかったので、調子に乗ってNAntのタスクにしてみた。
↓以下 ソース

using System;
using System.IO;
using System.Xml;
using System.Xml.XPath;
using System.Diagnostics;

using NAnt.Core;
using NAnt.Core.Attributes;

namespace NAnt.VS2005Tasks {
    [TaskName("clickonce")]
    public class ClickOnceTask : Task {
        private string version;
        private string projectFile;

        private string configuration = "Release";

        private XPathDocument document;
        private XPathNavigator navigator;
        private XmlNamespaceManager nsmgr;

        [TaskAttribute("version", Required=false)]
        public string Version {
            get { return version; }
            set { version = value; }
        }
        [TaskAttribute("projectfile", Required=true)]
        public string ProjectFile {
            get {
            	return Path.Combine(Project.BaseDirectory, projectFile);
            }
            set { projectFile = value; }
        }
        [TaskAttribute("configuration", Required=false)]
        public string Configuration {
            get { return configuration; }
            set {
                if(!string.IsNullOrEmpty(value)) configuration = value;
            }
        }
        private string ProjectName {
            get {
                return Path.GetFileNameWithoutExtension(projectFile);
            }
        }

        public ClickOnceTask() { }
        
        private XPathNavigator SelectSingleNode(string xpath) {
            return navigator.SelectSingleNode(xpath, nsmgr);
        }
        private string GetPublishURL() {
            XPathNavigator matchNode = SelectSingleNode("//D:Project/D:PropertyGroup/D:PublishUrl");

            if(matchNode == null) {
                throw new BuildException("プロジェクトファイルに[PublishUrl]要素が存在しません。");
            }
            return matchNode.Value;
        }

        private string GetOutputPath(string configuration) {
            XPathNavigator matchNode = SelectSingleNode(
                string.Format("//D:Project/D:PropertyGroup[contains(@Condition, '{0}')]/D:OutputPath", configuration)
            );
            if(matchNode == null) {
                throw new BuildException("指定したコンフィギュレーションに[OutputPath]要素が存在しません。");
            }
            return Path.Combine(Project.BaseDirectory, matchNode.Value);
        }

        private void XCopy(string srcPath, string destPath) {
            Directory.CreateDirectory(destPath);

            foreach(string file in Directory.GetFiles(srcPath)) {
                File.Copy(file,
                    Path.Combine(destPath, Path.GetFileName(file)), true
                );
            }
            foreach(string dir in Directory.GetDirectories(srcPath)) {
                XCopy(dir,
                    Path.Combine(destPath, Path.GetFileName(dir))
                );
            }
        }

        protected override void ExecuteTask() {
            string publishURL = GetPublishURL();
            string outputPath = GetOutputPath(Configuration);

            ProcessStartInfo startInfo = new ProcessStartInfo(
                Path.Combine(Environment.GetEnvironmentVariable("windir"), @"Microsoft.NET\Framework\v2.0.50727\MSBuild.exe")
            );
            startInfo.UseShellExecute = false;
            startInfo.RedirectStandardOutput = true;
            // 0: プロジェクトファイル名
            // 1: コンフィギュレーション
            // 2: ログレベル
            startInfo.Arguments = string.Format("\"{0}\" /t:Publish /p:Configuration={1} /nologo /verbosity:{2}",
                ProjectFile,
                Configuration,
                Verbose ? "d" : "n"     // d: 詳細  m: 普通
            );
            // 3: バージョン
            if(!string.IsNullOrEmpty(Version)) {
                startInfo.Arguments += string.Format(" /p:ApplicationVersion={0}", Version);
            }
            Process proc = Process.Start(startInfo);

            StreamReader stdout = proc.StandardOutput;
            // 順次出力
            while(!stdout.EndOfStream) {
                Log(Level.Info, stdout.ReadLine());
            }
            proc.WaitForExit();

            XCopy(
                Path.Combine(outputPath, ProjectName + ".publish"),
                publishURL
            );
        }

        protected override void InitializeTask(XmlNode taskNode) {
            base.InitializeTask(taskNode);

            if(!File.Exists(ProjectFile)) {
                throw new BuildException("プロジェクトファイルが存在しません。");
            }
            document = new XPathDocument(ProjectFile);
            navigator = document.CreateNavigator();
            nsmgr = new XmlNamespaceManager(navigator.NameTable);
            nsmgr.AddNamespace("D", "http://schemas.microsoft.com/developer/msbuild/2003");
        }
    }
}

それなりに汎用的にできたように思う。

使い方

ビルドファイル

<?xml version="1.0" encoding="shift-jis"?>
<project xmlns="http://cozy/0.85/nant.xsd"
    name="ClickOnceApp" basedir="."default="publish">

    <target name="publish">
        <clickonce projectfile="ClickOnceApp.csproj" version="1.1.0.0" />
    </target>

</project>

ちなみに今回のソースをPopflyで公開してみたので、興味のある方はこれをインストールして、見てあげてください。
プロジェクト名は「NAnt-VS2005Tasks」です。