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

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

.NET Framework 4.7.2 の場合は、外部参照ライブラリは 下記のように記述されています。

  <ItemGroup>
    <Compile Include="..\..\GazoYaroLib\GazoYaroImageProcessing\GazoYaroImageProcessing.cs">
      <Link>GazoYaroImageProcessing.cs</Link>
    </Compile>
  </ItemGroup>

.NET 9.0 の場合は、外部参照ライブラリは 下記のように記述されています。

  <ItemGroup>
    <Compile Include="..\..\GazoYaroLib\GazoYaroImageProcessing\GazoYaroImageProcessing.cs" Link="GazoYaroImageProcessing.cs" />
  </ItemGroup>

微妙に違っていて面倒ですね。

どちらにも対応できるのが下記の GetFilePathListOfExternalLibrary(); メソッドになります。

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

namespace tool_get_extlib_filepath
{
	internal class Program
	{

		static String FileNameExe = "";

		static int Main( string [] args )
		{

			FileNameExe = Path.GetFileName( Assembly.GetExecutingAssembly().Location );

			if ( args.Length != 2 )
			{
				Console.WriteLine( "実行時引数は2個必要です." );

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

			String fp_prj = args[0];
			String fp_gen = args[1];

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

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

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

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

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

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

			// 外部参照ファイルがあったかどうか.
			if ( ListFpExtLib.Count > 0 )
			{
				// ソートする.(必ずしもソートは必要ではない).
				ListFpExtLib.Sort();
			}

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

			// ビルドに突入する.
			Console.WriteLine( $"ExitCode 0, {FileNameExe}" );
			return 0;

		}

		static bool GetFilePathListOfExternalLibrary( List<string> list_file_path_external_library, string filepath_project )
		{

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

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

			string dirpath_project = Path.GetDirectoryName( filepath_project );
			XmlDocument doc = new XmlDocument();

			try
			{
				doc.Load( filepath_project );
			}
			catch
			{
				return false;
			}

			// 名前空間の有無を判定する.
			bool flag_has_namespace = false;
			string namespace_uri = "";
			XmlNamespaceManager nmsp_manager = new XmlNamespaceManager( doc.NameTable );

			XmlElement root = doc.DocumentElement;

			// ns はユーザが決める適当な名前.
			const string TMP_NMSP = "ns";

			if ( root != null )
			{
				if ( root.NamespaceURI != null )
				{
					if ( root.NamespaceURI.Length > 0 )
					{
						flag_has_namespace = true;

						namespace_uri = root.NamespaceURI;
						nmsp_manager.AddNamespace( TMP_NMSP, namespace_uri );
					}
				}
			}

			// <Compile> ノードを取得する.
			XmlNodeList node_list_COMPILE = null;

			if ( flag_has_namespace )
			{
				// .NET Framework 4.7.2 用.
				node_list_COMPILE = doc.SelectNodes( $"//{TMP_NMSP}:Compile", nmsp_manager );
			}
			else
			{
				// .NET 9.0 用.
				node_list_COMPILE = doc.SelectNodes( "//Compile" );
			}

			// <Compile> ノードがあったかどうか.
			if ( node_list_COMPILE == null || node_list_COMPILE.Count == 0 )
			{
				// 無かったので脱出.
				return false;
			}

			// <Compile> ノードリストをループで評価する.
			for ( int k = 0; k < node_list_COMPILE.Count; k++ )
			{

				// ノードを一つ取り出す.
				XmlNode node = node_list_COMPILE [k];


				if ( node != null )
				{

					XmlAttribute attr_INCLUDE = node.Attributes [ "Include" ];

					// Includeアトリビュートが存在するかどうか.
					if ( attr_INCLUDE != null )
					{

						// ここは ../../ の相対パスが取得される.
						// ただしこれだけでは、とりこんだライブラリ or 外部参照ライブラリ が判断できない.
						string fp_ext_lib_relative = attr_INCLUDE.Value;

						// 相対パスが空白でなければ解析を続ける.
						if (!( string.IsNullOrWhiteSpace( fp_ext_lib_relative )))
						{

							// フラグ.
							bool flag_has_link = false;

							// "Link" アトリビュートがあるかどうか.
							XmlAttribute attr_LINK = node.Attributes ["Link"];

							// Link アトリビュートがない場合(4.7.2)、と、ある場合(.NET9).
							if ( attr_LINK == null )
							{

								// .NET 4.7.2 の場合は Link アトリビュートがない代わりに,.
								// 子ノードに <Link> が存在すると外部参照ライブラリである.

								XmlNode node_LINK = null;

								if ( flag_has_namespace )
								{
									node_LINK = node.SelectSingleNode( $"{TMP_NMSP}:Link", nmsp_manager );
								}
								else
								{
									node_LINK = node.SelectSingleNode( "Link" );
								}

								if ( node_LINK != null )
								{
									// リンクが書いてあるフラグを true へ.
									flag_has_link = true;
								}

							}
							else
							{

								// .NET 9.0の場合は "Include" アトリビュートと、.
								// "Link" アトリビュートが同一行に書いてあると外部参照ライブラリである.

								if ( !string.IsNullOrWhiteSpace( attr_LINK.Value ) )
								{
									// リンクが書いてあるフラグを true へ.
									flag_has_link = true;
								}

							}

							// 外部参照ライブラリが存在する場合.
							if ( flag_has_link )
							{

								// 現在位置を基準にして、それの ../../../ と結合して、絶対パスを取得する.
								string fp_ext_lib_abs = Path.GetFullPath( Path.Combine( dirpath_project, fp_ext_lib_relative ) );

								// 実際にそのファイルがあるかどうか.
								if ( File.Exists( fp_ext_lib_abs ) )
								{
									// あればリストに追加して引数に戻す.
									list_file_path_external_library.Add( fp_ext_lib_abs );
								}

							}

						}
					}
				}

			}

			return true;

		}
	}
}

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

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

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

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

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