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

ビルド前イベントで、現在のプロジェクトで外部参照しているファイルのパスを取得してみましょう。

例えばプロジェクトの名称が aaa だった場合は、そのアプリケーションは aaa.csproj の中身に書いてある通りにビルドされます。ビルド対象の cs ファイルは、ここにすべて記述されています。例えば下記のようになります。ファイル形式は XML です。

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{7479A5A6-879C-4247-96CE-B87A4374F9FD}</ProjectGuid>
    <OutputType>WinExe</OutputType>
    <RootNamespace>aaa</RootNamespace>
    <AssemblyName>aaa</AssemblyName>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
    <Deterministic>true</Deterministic>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
    <DebugSymbols>true</DebugSymbols>
    <OutputPath>..\out\x64\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <DebugType>full</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
    <Prefer32Bit>true</Prefer32Bit>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
    <OutputPath>..\out\x64\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <Optimize>true</Optimize>
    <DebugType>pdbonly</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
    <Prefer32Bit>true</Prefer32Bit>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Data" />
    <Reference Include="System.Deployment" />
    <Reference Include="System.Drawing" />
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Windows.Forms" />
    <Reference Include="System.Xml" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="..\..\GazoYaroLib\GazoYaroBitmapUtility\GazoYaroBitmapUtility.cs">
      <Link>GazoYaroBitmapUtility.cs</Link>
    </Compile>
    <Compile Include="..\..\GazoYaroLib\GazoYaroImageProcessing\GazoYaroImageProcessing.cs">
      <Link>GazoYaroImageProcessing.cs</Link>
    </Compile>
    <Compile Include="FormMain.cs">
      <SubType>Form</SubType>
    </Compile>
    <Compile Include="FormMain.Designer.cs">
      <DependentUpon>FormMain.cs</DependentUpon>
    </Compile>
    <Compile Include="ApplicationUtility.cs" />
    <Compile Include="Program.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
    <EmbeddedResource Include="FormMain.resx">
      <DependentUpon>FormMain.cs</DependentUpon>
    </EmbeddedResource>
    <EmbeddedResource Include="Properties\Resources.resx">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
      <SubType>Designer</SubType>
    </EmbeddedResource>
    <Compile Include="Properties\Resources.Designer.cs">
      <AutoGen>True</AutoGen>
      <DependentUpon>Resources.resx</DependentUpon>
    </Compile>
    <None Include="Properties\Settings.settings">
      <Generator>SettingsSingleFileGenerator</Generator>
      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
    </None>
    <Compile Include="Properties\Settings.Designer.cs">
      <AutoGen>True</AutoGen>
      <DependentUpon>Settings.settings</DependentUpon>
      <DesignTimeSharedInput>True</DesignTimeSharedInput>
    </Compile>
  </ItemGroup>
  <ItemGroup>
    <None Include="App.config" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

ここで注目してもらいたいのは52行目と55行目です。外部参照しているファイルは <Compile> ノードの配下に <Link> ノードが存在するものがそれにあたります。そのファイルパスは Include アトリビュートで示されています。多くの場合は相対パスで示されています。

ここでは2個のファイルが外部参照されていることがわかります。

GazoYaroBitmapUtility.cs
GazoYaroImageProcessing.cs

がそれにあたります。

では、C# で独自のツールを作成して、この二つファイルパスをビルド前イベントでリストアップしてみましょう。下記がソースコードです。アプリケーションの名称は tool.exe とします。

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

namespace tool
{
	internal class Program
	{
		static int Main( string [] args )
		{

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

			String filepath_prj = args[1];

			if ( !( File.Exists( filepath_prj )) )
			{
				Console.WriteLine( filepath_prj );
				Console.WriteLine( "対象のプロジェクトファイルがありません." );
				return -1; // ビルドしない.
			}

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

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

			// これはユーザーが適当に決める.
			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 );

			// 見つからなくても null を戻さず、Count == 0 のリストを戻す.
			if ( node_list.Count <= 0 )
			{
				return  -1; // ビルドしない.
			}

			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 fp_absolute = Path.GetFullPath( fp_relative );

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

				}

			}

			if ( ListFpExtLib.Count > 0 )
			{
				for ( int k = 0; k < ListFpExtLib.Count; k++ )
				{
					String fp = ListFpExtLib[k];
					String fnm = Path.GetFileName( fp );
					
					// タブ区切りで出力する.
					Console.WriteLine( $"{fnm}\t{fp}" );
				}
			}
			else
			{
				Console.WriteLine( "外部参照ライブラリはありませんでした." );
			}

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

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

		}
	}
}

出来上がった tool.exe を aaa.csproj と同じディレクトリに配置してビルド前イベントとして実行してみましょう。

ビルド前イベントのコマンドラインは下記のようにします。tool.exe に aaa.csproj のパスを与えます。VisualStudio の動的マクロ変数 $(ProjectPath) としてパスを与えてください。

echo プリビルドスクリプトを開始します.

@echo off

REM ツールにプロジェクトのパスを実行時引数で与える.
call "$(ProjectDir)tool.exe" "$(ProjectPath)"

echo プリビルドスクリプトが完了しました.

実行すると下記のようになります。

リビルドを開始しました...
>------ すべてのリビルド開始: プロジェクト:aaa, 構成: Debug Any CPU ------
>  プリビルドスクリプトを開始します.
>  GazoYaroBitmapUtility.cs        C:\tmp\GazoYaroLib\GazoYaroBitmapUtility\GazoYaroBitmapUtility.cs
>  GazoYaroImageProcessing.cs      C:\tmp\GazoYaroLib\GazoYaroImageProcessing\GazoYaroImageProcessing.cs
>  プリビルドスクリプトが完了しました.
>  aaa -> C:\tmp\sss\aaa\bin\Debug\aaa.exe
========== すべて再構築: 1 正常終了、0 失敗、0 スキップ ==========
========== リビルド は 9:20 に開始され、00.923 秒 かかりました ==========