LoginSignup
8
4

More than 3 years have passed since last update.

MSBuild csproj 小ネタ集

Last updated at Posted at 2019-10-18

MSBuild の各要素・アイテム・プロパティーの俯瞰と逆引き大辞典 を昔書きましたが…
Qiita では非常にコードを掲載しやすい有り難さがあるので、具体的に実装しようという話です。

ItemGroup をタスク (別々に実行) へ結びつける

つぎのように 1 入力 1 出力のコマンドは容易だと思いますが、

EncFile input.xml bin\Debug\output.bin

IVKey などを個別に持たせるには?

EncFile2 encode IV Key input.xml bin\Debug\output.bin

メタデータを活用します。参考: プロジェクト ファイルで項目メタデータを参照する

1.csproj
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
         ToolsVersion="15.0">
  <PropertyGroup>
    <OutputPath>bin\Debug\</OutputPath>
  </PropertyGroup>
  <ItemGroup>
    <EncFile2 Include="first.xml">
      <OutputTo>first.dat</OutputTo>
      <IV>IV111</IV>
      <Key>Key111</Key>
    </EncFile2>
    <EncFile2 Include="second.xml">
      <OutputTo>second.dat</OutputTo>
      <IV>IV222</IV>
      <Key>Key222</Key>
    </EncFile2>
  </ItemGroup>
  <Target Name="BuildEncFile"
          AfterTargets="Build">
    <Exec Command="EncFile2.exe ^
      encode ^
      &quot;%(EncFile2.IV)&quot; ^
      &quot;%(EncFile2.Key)&quot; ^
      &quot;%(EncFile2.Identity)&quot; ^
      &quot;$(OutputPath)%(EncFile2.OutputTo)&quot;"/>
  </Target>
</Project>

%(EncFile2.Identity) の意は アイテム EncFile2 のメタデータ Identity を参照 です。

実行してみます:

H:\Proj\MSBuildCSProj\1>msbuild
.NET Framework 向け Microsoft (R) Build Engine バージョン 15.9.21+g9802d43bc3
Copyright (C) Microsoft Corporation.All rights reserved.

2019/10/18 21:50:08 にビルドを開始しました。
ノード 1 上のプロジェクト "H:\Proj\MSBuildCSProj\1\1.csproj" (既定のターゲット)。
BuildEncFile:
  EncFile2.exe ^
        encode ^
        "IV111" ^
        "Key111" ^
        "first.xml" ^
        "bin\Debug\first.dat"
  EncFile2.exe        encode       "IV111"       "Key111"       "first.xml"       "bin\Debug\first.dat"
  EncFile2.exe ^
        encode ^
        "IV222" ^
        "Key222" ^
        "second.xml" ^
        "bin\Debug\second.dat"
  EncFile2.exe        encode       "IV222"       "Key222"       "second.xml"       "bin\Debug\second.dat"
プロジェクト "H:\Proj\MSBuildCSProj\1\1.csproj" (既定のターゲット) のビルドが完了しました。


ビルドに成功しました。
    0 個の警告
    0 エラー

経過時間 00:00:00.20

プロパティ関数で既定値を適用

プロパティ関数 を使い、既定値を適用できるようにしましょう。

<EncFile2 Include="first.xml" />
<EncFile2 Include="$([MSBuild]::ValueOrDefault(`$(firstYml)`, `first.yml`))" />

firstYml の指定は、set firstYml=1.yml または msbuild /p:firstYml=1.yml などです。

PDB 中に含まれるソースコードのパスを変更する

参考: PDB 中に含まれるソースコードのパスを相対パスに変えるための csproj 設定(Rosnly リポジトリ内を漁ってたらこういう書き方だった)

実施例:

  <PropertyGroup>
    <DeterministicSourceRoot>C:\yourApp\</DeterministicSourceRoot>
    <RepoRoot>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\'))</RepoRoot>
    <PathMap>$(RepoRoot)=$(DeterministicSourceRoot)</PathMap>
  </PropertyGroup>

ItemGroup をタスク (一度に実行) へ結びつける

参考: MSBuild バッチ

つぎのように、一度のコマンド実行で、複数ファイルを指定したい事案です:

signtool sign /a A.exe B.dll C.dll

アイテムの種類 を活用します。参考: プロジェクト ファイルの項目を参照する

2.csproj
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
         ToolsVersion="15.0">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"
          Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <OutputPath>bin\Debug\</OutputPath>
  </PropertyGroup>
  <ItemGroup>
    <PreSignTarget Include="$(TargetPath)" />
    <PreSignTarget Include="B.dll" />
    <PreSignTarget Include="C.dll" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <Target Name="PreSignTarget"
          BeforeTargets="CreateSetup">
    <Exec Condition=" $(SignOptions) != '' "
          Command="signtool.exe ^
                   sign ^
                   $(SignOptions) ^
                   @(PreSignTarget-&gt;'&quot;%(FullPath)&quot;', ' ')"
          />
  </Target>
  <Target Name="CreateSetup"
          AfterTargets="Build">
  </Target>
</Project>

@(PreSignTarget-&gt;'&quot;%(FullPath)&quot;', ' ') は XML の都合で実体参照を用いますが、
@(PreSignTarget->'"%(FullPath)"', ' ') のように解釈されるはずです。

アイテムの種類@(<ItemType>) または @(<ItemType>, '<separator>') の書式で展開できます。
参考: プロジェクト ファイルで項目メタデータを参照する

@(CppFiles -> '%(Filename).obj') のような指定は 変換 と呼ばれます。
参考: メタデータを使用してアイテムの種類を変換する

実行してみます:

H:\Proj\MSBuildCSProj\2>set SIGNOPTIONS=/a

H:\Proj\MSBuildCSProj\2>msbuild /t:CreateSetup
.NET Framework 向け Microsoft (R) Build Engine バージョン 15.9.21+g9802d43bc3
Copyright (C) Microsoft Corporation.All rights reserved.

2019/10/18 22:02:52 にビルドを開始しました。
ノード 1 上のプロジェクト "H:\Proj\MSBuildCSProj\2\2.csproj" (CreateSetup ターゲット)。
PreSignTarget:
  signtool.exe ^
    sign ^
    /a ^
    "H:\Proj\MSBuildCSProj\2\bin\Debug\2.exe" "H:\Proj\MSBuildCSProj\2\B.dll" "H:\Proj\MSBuildCSProj\2\C.dll"
  signtool.exe sign /a "H:\Proj\MSBuildCSProj\2\bin\Debug\2.exe" "H:\Proj\MSBuildCSProj\2\B.dll" "H:\Proj\MSBuildCSProj\2\C.dll"
プロジェクト "H:\Proj\MSBuildCSProj\2\2.csproj" (CreateSetup ターゲット) のビルドが完了しました。

ビルドに成功しました。
    0 個の警告
    0 エラー

経過時間 00:00:00.19

ItemGroup をタスク (個別実行と一度に実行) へ結びつける

NSIS で 2 種類 (通常版、スーパー版) のセットアップを作成したい。しかして署名は一度にしたい場合など。

実行イメージ:

makensis.exe /DOUTFILE=Setup_Normal.exe /DSUPER=0 Setup.nsi
makensis.exe /DOUTFILE=Setup_Super.exe  /DSUPER=1 Setup.nsi
signtool.exe sign /a "Setup_Normal.exe" "Setup_Super.exe"
3.csproj
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="15.0">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')"/>
  <PropertyGroup>
    <OutputPath>bin\Debug\</OutputPath>
  </PropertyGroup>
  <ItemGroup>
    <MakeNsis Include="Setup.nsi">
      <OutputTo>Setup_Normal.exe</OutputTo>
      <Options>/DSUPER=0</Options>
    </MakeNsis>
    <MakeNsis Include="Setup.nsi">
      <OutputTo>Setup_Super.exe</OutputTo>
      <Options>/DSUPER=1</Options>
    </MakeNsis>
  </ItemGroup>
  <Target Name="CreateSetup" AfterTargets="Build">
    <Exec Command="makensis.exe /DOUTFILE=%(MakeNsis.OutputTo) %(MakeNsis.Options) %(MakeNsis.Identity)"/>
    <Exec Command="signtool.exe sign $(SignOptions) @(MakeNsis-&gt;'&quot;%(OutputTo)&quot;', ' ')" Condition=" $(SignOptions) != '' "/>
  </Target>
</Project>

実行してみました:

H:\Proj\MSBuildCSProj\3>msbuild
.NET Framework 向け Microsoft (R) Build Engine バージョン 15.9.21+g9802d43bc3
Copyright (C) Microsoft Corporation.All rights reserved.

2019/10/18 22:48:31 にビルドを開始しました。
ノード 1 上のプロジェクト "H:\Proj\MSBuildCSProj\3\3.csproj" (CreateSetup ターゲット)。
CreateSetup:
  makensis.exe /DOUTFILE=Setup_Normal.exe /DSUPER=0 Setup.nsi
  makensis.exe /DOUTFILE=Setup_Super.exe /DSUPER=1 Setup.nsi
  signtool.exe sign /a "Setup_Normal.exe" "Setup_Super.exe"
プロジェクト "H:\Proj\MSBuildCSProj\3\3.csproj" (CreateSetup ターゲット) のビルドが完了しました。


ビルドに成功しました。
    0 個の警告
    0 エラー

経過時間 00:00:00.27

ItemGroup アイテム メタデータのテンプレート的な利用

<OutputTo>Setup_$(AssemblyVersion)_Normal.exe</OutputTo> のように、
メタデータの指定をテンプレート化したいが…

Target の中でしか解決できないなど、$(AssemblyVersion) を直ちに利用できない場合の回避策についてです。

Target 内部で %(MakeNsis.OutputToResolved) を定義することで回避します。

  <Target>
    <ItemGroup>
      <MakeNsis>
        <OutputToResolved>...</OutputToResolved>
      </MakeNsis>
    </ItemGroup>
  </Target>

有名な策として、String.Copy という静的メソッドを用い、プロパティ関数の体で文字列を加工し出力する方法があります。

<OutputToResolved>$([System.String]::Copy('%(OutputTo)')</OutputToResolved>

<OutputToResolved>$([System.String]::Copy('%(OutputTo)').Replace('{VER}', '$(Ver)'))</OutputToResolved>

<OutputToResolved>$([System.String]::Copy('%(OutputTo)').Replace('{VER}', '$(Ver.Replace('.', '_'))'))</OutputToResolved>

出来上がりです:

4.csproj
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="15.0">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')"/>
  <PropertyGroup>
    <OutputPath>bin\Debug\</OutputPath>
    <Ver>1.2.3</Ver>
  </PropertyGroup>
  <ItemGroup>
    <MakeNsis Include="Setup.nsi">
      <OutputTo>Setup_{VER}_Normal.exe</OutputTo>
      <Options>/DSUPER=0</Options>
    </MakeNsis>
    <MakeNsis Include="Setup.nsi">
      <OutputTo>Setup_{VER}_Super.exe</OutputTo>
      <Options>/DSUPER=1</Options>
    </MakeNsis>
  </ItemGroup>
  <Target Name="CreateSetup" AfterTargets="Build">
    <ItemGroup>
      <MakeNsis>
        <OutputToResolved>$([System.String]::Copy('%(OutputTo)').Replace('{VER}', '$(Ver.Replace('.', '_'))'))</OutputToResolved>
      </MakeNsis>
    </ItemGroup>
    <Exec Command="makensis.exe /DOUTFILE=%(MakeNsis.OutputToResolved) %(MakeNsis.Options) %(MakeNsis.Identity)"/>
    <Exec Command="signtool.exe sign $(SignOptions) @(MakeNsis-&gt;'&quot;%(OutputToResolved)&quot;', ' ')" Condition=" $(SignOptions) != '' "/>
  </Target>
</Project>

実行してみました:

H:\Proj\MSBuildCSProj\4>msbuild
.NET Framework 向け Microsoft (R) Build Engine バージョン 15.9.21+g9802d43bc3
Copyright (C) Microsoft Corporation.All rights reserved.

2019/10/27 3:25:40 にビルドを開始しました。
ノード 1 上のプロジェクト "H:\Proj\MSBuildCSProj\4\4.csproj" (既定のターゲット)。
CreateSetup:
  makensis.exe /DOUTFILE=Setup_1_2_3_Normal.exe /DSUPER=0 Setup.nsi
  makensis.exe /DOUTFILE=Setup_1_2_3_Super.exe /DSUPER=1 Setup.nsi
  signtool.exe sign /a "Setup_1_2_3_Normal.exe" "Setup_1_2_3_Super.exe"
プロジェクト "H:\Proj\MSBuildCSProj\4\4.csproj" (既定のターゲット) のビルドが完了しました。


ビルドに成功しました。
    0 個の警告
    0 エラー

経過時間 00:00:00.28

nupkg で、ファイルをコピー

.NET Core スタイルの csproj で nupkg を構築し (dotnet pack でビルド)、多数のファイルをビルド後にコピーする事案です。

ThePackage.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <ItemGroup>
    <None Pack="true" IncludeInPackage="false" Update="ThePackage.targets" PackagePath="build/ThePackage.targets">
    </None>

    <None Pack="true" IncludeInPackage="false" Update="GPL/**" PackagePath="binaryFiles">
    </None>
  </ItemGroup>
</Project>

ThePackage.targets

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <ThePackageFiles Include="$(MSBuildThisFileDirectory)/../binaryFiles/**"/>
  </ItemGroup>

  <Target Name="CopyThePackage" AfterTargets="AfterBuild">
    <Copy SourceFiles="@(ThePackageFiles)" DestinationFolder="$(OutDir)/%(RecursiveDir)" SkipUnchangedFiles="true" />
  </Target>
</Project>

Notes:
- nupkg の /binaryFiles フォルダを経由してコピーします。
- ThePackage をプロジェクト名で置き換えます。

8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4