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

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

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

using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Linq;

namespace tool
{
	internal class Program
	{

		static int Main( string [] args )
		{

			if ( args.Length <= 0 )
			{
				Console.WriteLine( "未コミットチェック対象のファイル群を記述したファイルが指定されていません." );

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

			String fp_gen = args[0];

			if ( !( File.Exists( fp_gen )) )
			{
				Console.WriteLine( fp_gen );
				Console.WriteLine( "未コミットチェック対象のファイル群を記述したファイルが存在しません" );

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

			List<String> ListFpExtLib = new List<String>(0);

			try
			{
				// 引数で与えられたファイルを読む.
				using ( StreamReader sr = new StreamReader( fp_gen, new UTF8Encoding( false )))
				{

					for (;;)
					{
						String line = sr.ReadLine();

						if ( line != null )
						{
							ListFpExtLib.Add( line );
						}
						else
						{
							break;
						}
					}
				}
			}
			catch ( Exception excp )
			{
				Console.WriteLine( excp.Message );

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

			// いったん対象の外部参照ライブラリの情報をループで出力する.
			for ( int k = 0; k < ListFpExtLib.Count; k++ )
			{
				// 参照ライブラリのファイルパス.
				Console.WriteLine( ListFpExtLib[k] );
			}

			// gitコマンドを開始することを知らせる.
			Console.WriteLine( "git command begin." );

			List<String> ListDirpathUnClean = new List<string>(0);

			// 外部参照ライブラリが配置されているディレクトリで Git コマンドを発行する.
			for ( int k = 0; k < ListFpExtLib.Count; k++ )
			{

				// 外部参照ライブラリのファイルパス.
				String fp = ListFpExtLib[k];
					
				// そのファイルと同じ階層にgitリポジトリが存在するか?
				if ( IsGitRepositoryInSameDirectoryAsFile( fp ))
				{

					// .git リポジトリが含まれているディレクトリで git コマンドを実行させたい.
					String dir_working = Path.GetDirectoryName( fp );

					// そのディレクトリがあるかどうか.
					if ( Directory.Exists( dir_working ))
					{

						// そのディレクトリに変更がかかったファイルがあったかどうか.
						if ( !( IsWorkingDirectoryClear( dir_working )))
						{
							// 変更がかかったファイルがあるワーキングディレクトリを記録する.
							ListDirpathUnClean.Add( dir_working );
						}

					}

				}

			}

			// gitコマンドを終了することを知らせる.
			Console.WriteLine( "git command end." );

			if ( ListDirpathUnClean.Count == 0 )
			{
				Console.WriteLine( "外部参照ライブラリのコミットはクリーンです." );
			}
			else
			{

				Console.WriteLine( "未コミットの外部参照ライブラリがありました,下記にリスト表示します." );

				for ( int k = 0; k < ListDirpathUnClean.Count; k++ )
				{
					Console.WriteLine( ListDirpathUnClean[k] );

					// ディレクトリをひらいてもいい.
					try
					{
						Process.Start( ListDirpathUnClean[k] );
					}
					catch ( Exception excp )
					{
						Console.Write( excp.Message );
					}

				}

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

			// デバッグで止めたいときはこれをコメントイン.
			//Console.ReadKey();

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

		}

		// 同一の階層にgitリポジトリがあるかどうか.
		static bool IsGitRepositoryInSameDirectoryAsFile( string filepath )
		{

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

			if ( filepath == "" )
			{
				return false;
			}

			string dir = Path.GetDirectoryName( filepath );
			if ( dir == null || dir == "" )
			{
				return false;
			}

			string dir_dot_git = Path.Combine( dir, ".git" );
			bool ret = Directory.Exists( dir_dot_git );

			return ret;

		}

		// gitワーキングディレクトリ内のファイル群に変更がかかっていないかどうか.
		static bool IsWorkingDirectoryClear( string dir_working )
		{

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

			if ( dir_working == "" )
			{
				return false;
			}

			const String ARGV_A = "diff --name-only";
			const String ARGV_B = "diff --cached --name-only";

			// ワーキングツリーに変更があったかどうか?
			// 変更がなければ何も戻ってこない(UNIX的ダンマリ).
			List<String> retout_a = ExecGitCommand( ARGV_A, dir_working );

			// ステージに変更があったかどうか?
			// 変更がなければ何も戻ってこない(UNIX的ダンマリ).
			List<String> retout_b = ExecGitCommand( ARGV_B, dir_working );

			if (( retout_a.Count == 0 ) && ( retout_b.Count == 0 ))
			{
				return true;
			}
			else
			{
				return false;
			}

		}

		// git のコマンドを実行する.
		static List<string> ExecGitCommand( string arguments, string working_directory )
		{

			ProcessStartInfo psi = new ProcessStartInfo();
			psi.FileName               = "git";
			psi.Arguments              = arguments;
			psi.WorkingDirectory       = working_directory;
			psi.RedirectStandardOutput = true;
			psi.RedirectStandardError  = true;
			psi.StandardOutputEncoding = Encoding.UTF8; // ここを指定しないと日本語は文字化けする.
			psi.StandardErrorEncoding  = Encoding.UTF8; // ここを指定しないと日本語は文字化けする.
			psi.UseShellExecute        = false;
			psi.CreateNoWindow         = true;

			List<String> output = new List<string>();

			try
			{

				using ( Process proc = Process.Start( psi ) )
				{

					while ( !proc.StandardOutput.EndOfStream )
					{
						output.Add( proc.StandardOutput.ReadLine() );
					}

					string err = proc.StandardError.ReadToEnd();
					proc.WaitForExit();

					if ( proc.ExitCode != 0 )
					{
						throw new Exception( err );
					}

				}

			}
			catch ( Exception excp )
			{
				output.Add( excp.Message );
			}

			return output;

		}

		// デバッグ用のメソッド. 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)external_lib_info.gen" がそれにあたります。

"$(ProjectDir)tool.exe" "$(ProjectDir)external_lib_info.gen"

もし未コミットのライブラリファイルがあった場合は、VisualStudio では下記のような出力になり、かつ、そのディレクトリが自動的に開きます。

リビルドを開始しました...
1>------ すべてのリビルド開始: プロジェクト:aaa, 構成: Release x64 ------
1>  C:\develop\GazoYaroLib\GazoYaroBitmapUtility\GazoYaroBitmapUtility.cs
1>  C:\develop\GazoYaroLib\GazoYaroImageProcessing\GazoYaroImageProcessing.cs
1>  git command begin.
1>  git command end.
1>  未コミットの外部参照ライブラリがありました,下記にリスト表示します.
1>  C:\develop\GazoYaroLib\GazoYaroImageProcessing
1>C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\amd64\Microsoft.Common.CurrentVersion.targets(1396,5): error MSB3073: コマンド ""C:\develop\sss\aaa\tool.exe" "C:\develop\sss\aaa\external_lib_info.gen"" はコード -1 で終了しました。
========== すべて再構築: 0 正常終了、1 失敗、0 スキップ ==========
========== リビルド は 13:11 に開始され、00.466 秒 かかりました ==========

未コミットのライブラリファイルが存在しなかった場合は下記のような出力になります。

リビルドを開始しました...
1>------ すべてのリビルド開始: プロジェクト:aaa, 構成: Release x64 ------
1>  C:\develop\GazoYaroLib\GazoYaroBitmapUtility\GazoYaroBitmapUtility.cs
1>  C:\develop\GazoYaroLib\GazoYaroImageProcessing\GazoYaroImageProcessing.cs
1>  git command begin.
1>  git command end.
1>  外部参照ライブラリのコミットはクリーンです.
1>  aaa -> C:\develop\sss\out\x64\Release\aaa.exe
========== すべて再構築: 1 正常終了、0 失敗、0 スキップ ==========
========== リビルド は 13:12 に開始され、00.512 秒 かかりました ==========