ビルド前イベントを活用する、その14

ビルド前イベントを使って、現在のプロジェクトファイル *.csproj を解析して外部参照ライブラリのパスをファイルに保存するツールを作ってみましょう。

using System;
using System.Collections.Generic;
using System.Xml;
using System.IO;
using System.Text;

using System.Runtime.CompilerServices;

namespace tool
{
	internal class Program
	{

		// 外部参照ライブラリを列挙するファイルの名前.
		const String FILENAME_EXT_LIB_INFO = "external_lib_info.gen";

		static int Main( string [] args )
		{

			if ( args.Length <= 0 )
			{
				Console.WriteLine( "プロジェクトファイルのパスが指定されていません." );

				// ビルドを中止する.
				return -1;
			}

			String filepath_prj = args[0];

			if ( !( File.Exists( filepath_prj )) )
			{
				Console.WriteLine( filepath_prj );
				Console.WriteLine( "対象のプロジェクトファイルがありません." );

				// ビルドを中止する.
				return -1;
			}

			// プロジェクトファイルが含まれているディレクトリを取得する.
			String dir_prj = Path.GetDirectoryName( filepath_prj );

			// ここに外部参照ライブラリのファイルパスがリストアップされる.
			List <String> ListFpExtLib = new List<String>(0);

			// そのプロジェクトファイルに記述されている外部参照ライブラリのパスをリスト取得する.
			bool ret = GetFilePathListOfExternalLibrary( ListFpExtLib, filepath_prj );
			if ( !ret )
			{
				Console.WriteLine( $"プロジェクトファイルの解析に失敗しました.\t{filepath_prj}" );

				// ビルドを中止する.
				return -1;
			}

			// すべてプロジェクトに取り込んだライブラリを利用しているときは、.
			// true でもどってきても ListFpExtLib の個数がゼロである.

			// 外部参照ファイルがあったかどうか.
			if ( ListFpExtLib.Count > 0 )
			{
				// ソートする.
				ListFpExtLib.Sort();
			}

			// プロジェクトファイルと同じ階層に外部参照ライブラリの情報が記録されたファイルを配置したい.
			String fp_out = Path.Combine( dir_prj, FILENAME_EXT_LIB_INFO );

			// ListFpExtLib の個数が0であってもファイル自体はつくる.
			using ( StreamWriter sw = new StreamWriter( fp_out, false, new UTF8Encoding( false )))
			{
				for ( int k = 0; k < ListFpExtLib.Count; k++ )
				{
					sw.WriteLine( ListFpExtLib[k] );
				}
			}

			// ビルドに突入する.
			return 0;

		}

		// ここからメソッド.

		static bool GetFilePathListOfExternalLibrary( List<String> ListFilePathExternalLibrary, String filepath_project )
		{

			if ( ListFilePathExternalLibrary == null )
			{
				return false;
			}

			if ( !File.Exists( filepath_project ))
			{
				return false;
			}

			String dirpath_project = Path.GetDirectoryName( filepath_project );

			// XMLドキュメントを読み込む.
			XmlDocument doc = new XmlDocument();
			doc.Load( filepath_project );

			// これはユーザーが適当に決める.
			const String NAME_SPACE = "ns";

			// うまくいかないときは csproj ファイルを目視して確認せよ.
			const String URI = "http://schemas.microsoft.com/developer/msbuild/2003";

			// 名前空間マネージャを作成する.
			XmlNamespaceManager nmsp_mngr = new XmlNamespaceManager( doc.NameTable );
			nmsp_mngr.AddNamespace( NAME_SPACE, URI );

			// <Compile>というすべてのノードをリストとして取得する.
			String xpath_COMPILE = $"//{NAME_SPACE}:Compile";
			XmlNodeList node_list = doc.SelectNodes( xpath_COMPILE, nmsp_mngr );

			// <Compile>というノードがないXMLファイルはプロジェクトファイルではない.
			if ( node_list.Count <= 0 )
			{
				return  false;
			}

			for ( int k = 0; k < node_list.Count; k++ )
			{

				// <Compile> ノードをひとつ取り出す.
				XmlNode node = node_list[k];

				// そこに <Link> という子ノードがあるかチェックする.
				String xpath_LINK = $"{NAME_SPACE}:Link";
				XmlNode node_child = node.SelectSingleNode( xpath_LINK, nmsp_mngr );
				if ( node_child != null )
				{
					// 子ノードの親、つまり <Compile> の Include アトリビュートを参照する.
					XmlAttribute attr = node.Attributes [ "Include" ];

					// そのアトリビュートがあるかどうか.
					if ( attr != null )
					{
						// 相対パスで記述されている可能性が高い.
						String fp_relative = attr.Value;

						// 絶対パスに変換する.
						String tmp = Path.Combine( dirpath_project, fp_relative );
						String fp_absolute = Path.GetFullPath( tmp );

						// リストに追加する.
						ListFilePathExternalLibrary.Add( fp_absolute );
					}

				}

			}

			return true;

		}

		// デバッグ用のメソッド. C言語の __FILE__ や __LINE__ にあたる.
		static class Log
		{
			public static void Mark(
				String str_msg = "",
				[CallerFilePath] String file_path = "",
				[CallerLineNumber] int line_number = 0,
				[CallerMemberName] String member_name = ""
				)
			{
				String file_name = Path.GetFileName( file_path );
				Console.WriteLine( $"[{file_name}:{line_number}] {member_name}() {str_msg}." );
			}
		}

	}
}

VisualStudio のビルド前コマンドは下記のようにします。tool.exe はプロジェクトファイルと同じ場所 $(ProjectDir) に置いてください。実行時引数は解析対象のプロジェクトファイルを渡してください。$(ProjectPath) がそれにあたります。

"$(ProjectDir)tool.exe" "$(ProjectPath)"

生成されるファイルは下記になります。

C:\develop\GazoYaroLib\GazoYaroBitmapUtility\GazoYaroBitmapUtility.cs
C:\develop\GazoYaroLib\GazoYaroImageProcessing\GazoYaroImageProcessing.cs

これらのファイルが Git のリポジトリで履歴を管理されていたら、git コマンドを発行していろいろな履歴情報を参照することができますね。