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

ビルド前イベントで、ライブラリ群すべてのコミット状態を確認するツールを動かしてみましょう。下記のコードがそれです。実行時引数でライブラリ群が格納されているディレクトリパスを指定してください。

ライブラリ群が多数ある場合( Gitリポジトリが多数ある場合 )は、Gitのコマンドを連発するので若干の待たされ感がありますのでご注意ください。

ビルド前イベントにいれておけば、全ライブラリのコミット状態の確認を必ず実施するので好ましいですが、確認が完了するまでイライラするかもしれません。

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

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

namespace tool
{
	internal class Program
	{

		static int Main( string [] args )
		{

			if ( args.Length <= 0 )
			{
				Console.WriteLine( "参照ライブラリ群のディレクトリパスが指定されていません." );

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

			String dir_top = args[0];

			if ( !( Directory.Exists( dir_top )) )
			{
				Console.WriteLine( dir_top );
				Console.WriteLine( "参照ライブラリ群のディレクトリパスが存在しません" );

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

			// cs ファイルのパスをすべて取得する.
			SearchOption sop = SearchOption.AllDirectories;
			String [] ListFpExtLib = Directory.GetFiles( dir_top, "*.cs", sop );

			if ( ListFpExtLib.Length == 0 )
			{
				Console.WriteLine( dir_top );
				Console.WriteLine( "*.cs ファイルが存在しませんでした." );

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

			// ソートする.
			Array.Sort( ListFpExtLib );

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

			// 後ほどファイル保存するときに使うストリングビルダを生成する.
			StringBuilder sb = new StringBuilder();

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

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

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

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

					String dir_working = Path.GetDirectoryName( fp );

					if ( Directory.Exists( dir_working ))
					{

						// そのディレクトリに変更がかかったファイルがあったかどうか.
						if ( IsWorkingDirectoryClear( dir_working ) )
						{

							// そのディレクトリ内のファイル群のコミットハッシュを取得する.
							String hash = "";
							String branch = "";
							if ( GetCommitHashAndBranchName( dir_working, ref hash, ref branch ))
							{
								// 取得されたコミットハッシュとブランチ名とワーキングディレクトリ.
								String str_line = $"{dir_working}\t{hash}\t{branch}";

								// VisualStudioコンソールに出力する.
								Console.WriteLine( str_line );

								// 後ほどファイル保存するときに使うストリングビルダにも追加する.
								sb.AppendLine( str_line );
							}
							else
							{
								Console.WriteLine( $"ハッシュまたはブランチ名が取得できませんでした.\t{dir_working}" );

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

						}
						else
						{
							// 変更がかかったファイルがあるワーキングディレクトリを記録する.
							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 bool GetCommitHashAndBranchName(
						String dir_working,
						ref String commit_hash,
						ref String branch_name
						)
		{

			String dir_dot_git = Path.Combine( dir_working, ".git" );
			if ( !Directory.Exists( dir_dot_git ))
			{
				return false; // warning.
			}

			// A: アクティブブランチのコミットハッシュを取得する.
			// B: アクティブブランチの名前を取得する.
			const String ARGV_A = "rev-parse HEAD";
			const String ARGV_B = "rev-parse --abbrev-ref HEAD";

			// Gitコマンドを実行する.
			List<String> outA = ExecGitCommand( ARGV_A, dir_working );
			List<String> outB = ExecGitCommand( ARGV_B, dir_working );

			if ( outA.Count == 0 || outA[0].Contains( "fatal:" ))
			{
				commit_hash = "";
				branch_name = "";
				return false; // warning.
			}

			if ( outB.Count == 0 || outB[0].Contains( "fatal:" ))
			{
				commit_hash = "";
				branch_name = "";
				return false; // warning.
			}

			// 引数に戻す.
			commit_hash = outA[0];
			branch_name = outB[0];

			return true;;

		}

		// 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) に置いてください。実行時引数はライブラリ群が格納されているディレクトリを指定してください。

call "$(ProjectDir)tool.exe" "e:\mylib"

ワーキングディレクトリがクリーンだった場合、つまり未コミットのファイルがない場合は、下記のようなビルド出力が得られます。

リビルドを開始しました...
1>------ すべてのリビルド開始: プロジェクト:aaa, 構成: Release x64 ------
1>  E:\mylib\_GazoYaroAccumulatedTimeRecorder\GazoYaroAccumulatedTimeRecorder.cs
1>  E:\mylib\_GazoYaroAnotherWindowControl\GazoYaroAnotherWindowControl.cs
1>  E:\mylib\_GazoYaroArrayOneDim\GazoYaroArrayOneDim.cs
1>  E:\mylib\_GazoYaroBlob\GazoYaroBlob.cs
1>  git command begin.
1>  E:\mylib\_GazoYaroAccumulatedTimeRecorder	d7e33d399f7c474e0975736a8da3cffb1520333b	master
1>  E:\mylib\_GazoYaroAnotherWindowControl	9971aa2f2fd00cb6a789643ebd4e79883b699679	master
1>  E:\mylib\_GazoYaroArrayOneDim	e7d6455699a279c2711de79c00a5cbceaf5d0d21	dev
1>  E:\mylib\_GazoYaroBlob	bee84a24b9ec0739f18cd49244185d7c6335d2ea	master
1>  git command end.
1>  外部参照ライブラリのコミットはクリーンです.
1>  aaa -> C:\develop\sss\out\x64\Release\aaa.exe
========== すべて再構築: 1 正常終了、0 失敗、0 スキップ ==========
========== リビルド は 11:15 に開始され、09.241 秒 かかりました ==========

もし、未コミットのものがあった場合は

リビルドを開始しました...
1>------ すべてのリビルド開始: プロジェクト:aaa, 構成: Release x64 ------
1>  E:\mylib\_GazoYaroAccumulatedTimeRecorder\GazoYaroAccumulatedTimeRecorder.cs
1>  E:\mylib\_GazoYaroAnotherWindowControl\GazoYaroAnotherWindowControl.cs
1>  E:\mylib\_GazoYaroArrayOneDim\GazoYaroArrayOneDim.cs
1>  E:\mylib\_GazoYaroBlob\GazoYaroBlob.cs
1>  git command begin.
1>  E:\mylib\_GazoYaroAccumulatedTimeRecorder	d7e33d399f7c474e0975736a8da3cffb1520333b	master
1>  E:\mylib\_GazoYaroAnotherWindowControl	9971aa2f2fd00cb6a789643ebd4e79883b699679	master
1>  E:\mylib\_GazoYaroArrayOneDim	e7d6455699a279c2711de79c00a5cbceaf5d0d21	dev
1>  E:\mylib\_GazoYaroBlob	bee84a24b9ec0739f18cd49244185d7c6335d2ea	master
1>  git command end.
1>  未コミットの外部参照ライブラリがありました,下記にリスト表示します.
1>  E:\mylib\_GazoYaroArrayOneDim
1>  E:\mylib\_GazoYaroBlob
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" "E:\mylib"" はコード -1 で終了しました。
========== すべて再構築: 0 正常終了、1 失敗、0 スキップ ==========
========== リビルド は 11:26 に開始され、00.480 秒 かかりました ==========

ここでは return -1; を返してビルドを中止するようにしていますが、これはユーザごとに考えが違うと思いますのでご自身でご判断ください。