VC++ PowershellによるMSBuild実行

vc-msbuild-powershellのアイキャッチ画像 Windows

MSBuildはWindowsにおけるソースコードのコンパイル・ビルドコマンドであり、主にVisual Studioを使って開発する場合に使用されます。

ここでは、Powershellを使ってMSBuildを実行する方法を解説します。

PowershellによるMSBuild実行

Powershellを使って、プロジェクトファイル(*.vcxproj)をMSBuildでビルドします。Visual Studioのプロジェクト・プロパティファイルはこちらを参考に用意されている前提で解説します。

Powershellのファイルをbuild.ps1として用意し、以下のように実装します。

src/build.ps1

param(
	[String]$Mode="Build",		# 実行モード[Build|Rebuild|Clean]
	[String]$Config="Debug",	# ビルド構成[Debug|Release]
	[String]$Platform="x64",	# ターゲットプラットフォーム[x64]
	[String]$Develop="VS2019",	# 開発環境[VS2019]
	[String]$LogDirectory="$(Split-Path $myInvocation.MyCommand.Path -parent)\build2019\log",	# ログ格納ディレクトリ
	[Switch]$Help				# 使用方法表示
)

#--------------------------------------
# 変数定義
#--------------------------------------
$debugPreference='SilentlyContinue'
Set-Variable CUR_DIR "$(Split-Path $myInvocation.MyCommand.Path -parent)"
Set-Variable BUILD_DIR "$script:CUR_DIR\..\.."
Set-Variable START_TIME ([System.DateTime]::Now)
Set-Variable RESULT_MSG @()
Set-Variable VS2019_DIR 'C:\Program Files (x86)\Microsoft Visual Studio\Installer'
Set-Variable ENV_LIST   @()
Set-Variable PROJECT_LIST @()

#--------------------------------------
# 引数チェック
#--------------------------------------
# 使用方法表示
if ($script:Help) { Print-Usage; exit 1 }
# 動作モード
if ((($script:Mode -eq "Build") -or ($script:Mode -eq "Rebuild") -or ($script:Mode -eq "Clean")) -eq $false) { Print-Usage $script:Mode; throw }
# ビルド構成×プラットフォーム
if ((($script:Config -eq "Debug") -or ($script:Config -eq "Release")) -eq $false) { Print-Usage $script:Config; throw }
if ((($script:Platform -eq "x64")) -eq $false) { Print-Usage $script:Platform; throw }
# 開発環境
if ((($script:Develop -eq "VS2019")) -eq $false) { Print-Usage $script:Develop; throw }
# 暗黙の引数は受け付けない
if($args -ne $null){ Print-Usage $args; throw }

#--------------------------------------
# バッチファイル実行後の環境変数取得
#--------------------------------------
function Set-BatchEnviron([String]$filePath, [String]$cmdOption)
{
	# コマンドプロンプトで出力した結果をPowerShell上で設定する
	$execCommands = "`"$filePath`" $cmdOption & set"
	cmd /c $execCommands | Foreach-Object {
		$p, $v = $_.Split('=')
		Set-Item -path env:$p -value $v
	}
}

#--------------------------------------
# VS2019環境セットアップ
#--------------------------------------
function Set-VS2019Environ64()
{
	$batchFile = & "$VS2019_DIR\vswhere.exe" "-latest" "-products" "*" "-requires" "Microsoft.VisualStudio.Component.VC.Tools.x86.x64" "-property" "installationPath" | % { echo $_\VC\Auxiliary\Build\vcvarsall.bat }
	$cmdOption = "x64"
	Set-BatchEnviron $batchFile $cmdOption
	Write-Host "setup visual studio 2019 $cmdOption" -ForeGroundColor DarkGray
}

#--------------------------------------
# 初期化
#--------------------------------------
function Initialize()
{
	# 今の位置を覚えておく
	Push-Location

	# 環境変数を保存
	$script:ENV_LIST = $(Get-Item -Path env:)

	# Visual Studio環境設定
	switch ($script:Develop) {
		'VS2019' {
			$script:PRJ_EXTENSION    = ".vcxproj"
			$script:LogDirectory="$script:CUR_DIR\build2019\log"
		}
		default { Write-Error "internal error"; exit 1 }
	} # switch

	# ログ格納ディレクトリ作成
	if (Test-Path $script:LogDirectory) {
		Remove-Item $script:LogDirectory -recurse -force
	}
	New-Item $script:LogDirectory -type directory -force | Out-Null

	return 0
}

#--------------------------------------
# 終了処理
#--------------------------------------
function Finalize()
{
	# トータル時間出力
	Write-Host "`n<$script:Mode Time>"
	if ($script:RESULT_MSG) {
		$script:RESULT_MSG | Foreach-Object { Write-Host $_ }
	}
	Write-Host "Total      Time=$([System.DateTime]::Now - $script:START_TIME)"

	# 環境変数を元に戻す
	$script:ENV_LIST | Foreach-Object { Set-Item -Path env:$($_.Name) -Value $($_.Value) }

	# 元の位置に戻る
	Pop-Location
}

# ------------------------------------------------------------------------------
# プロセス実行
# ------------------------------------------------------------------------------
function Start-Process(
	[String]$ModulePath,
	[String[]]$Arguments
)
{
	$startInfo = New-Object System.Diagnostics.ProcessStartInfo
	$startInfo.FileName = $ModulePath
	$startInfo.UseShellExecute = $false	# ウィンドウが出てくると作業を妨げるので作成しない
	$startInfo.CreateNoWindow = $true	# ウィンドウが出てくると作業を妨げるので作成しない
	$startInfo.WorkingDirectory = $script:CUR_DIR
	$startInfo.Arguments = $Arguments
	# この返却値を受け取らないとプロセス終了制御できないので注意
	return [System.Diagnostics.Process]::Start($startInfo)
}

#--------------------------------------
# プロセス終了待ち
#--------------------------------------
function Wait-Process(
	[System.Diagnostics.Process]$Target=$(throw "please input target"),
    [String]$Suffix
)
{
	if(!$Target){
		return 0
	}

	Write-Debug "wait  process($($Target.Name)). arg=$($Target.StartInfo.Arguments)"
	if (($Target.WaitForExit()) -eq $false) {
		Write-Error "error wait for exit process. name=$($Target.Name)"
		return -1
	}
	Write-Debug "exit  process($($Target.Name)). arg=$($Target.StartInfo.Arguments), code=$($Target.ExitCode)"
	# 第一引数のソリューションファイルの一階層上のディレクトリ名を名前にする
	$name = ((($Target.StartInfo.Arguments).Split(' '))[0] | Split-Path | Split-Path -Leaf) + $Suffix
	Write-Host "$name $script:Mode Finished. ExitCode=$($Target.ExitCode), LapTime=$($Target.ExitTime - $Target.StartTime)" -ForeGroundColor DarkGray
	return 0
}

#--------------------------------------
# ビルド実行
#--------------------------------------
function Start-Build(
    [String]$Component=$(throw "please input Component"),
	[String]$ProjectFile=$(throw "please input SolutionFile"),
	[String]$Platform=$(throw "please input Platform"),
	[String]$Configuration=$(throw "please input Configuration"),
	[String]$Mode=$(throw "please input Mode"),
	[String]$Verbosity="detailed"
)
{
	Write-Host "Start [$Component] $ProjectFile $Mode $Configuration|$Platform" -ForeGroundColor Green
    $logName = "$script:LogDirectory\$Component"
	$process = Start-Process `
		-ModulePath "msbuild.exe"`
		-Arguments @($ProjectFile,`
					 "/v:$Verbosity",`
					 "/p:platform=$Platform",`
					 "/p:configuration=$Configuration",`
					 "/t:$Mode",`
					 "/flp1:LogFile=$logName.summary;Append;summarysonly",`
					 "/flp2:LogFile=$logName.error;Append;errorsonly",`
					 "/flp3:LogFile=$logName.warning;Append;warningsonly"
					 )
	if ($process -eq $null) {
		Write-Error "start build error(process start). name=$name"
		return $null
	} else {
		Write-Debug "start process($($process.Name)). arg=$($process.StartInfo.Arguments)"
		return $process
	}
}

#--------------------------------------
# メイン関数
#--------------------------------------
function Main()
{
	# Visual Studio環境設定
	switch ($script:Develop) {
		'VS2019' {
			switch ($script:Platform) {
				'x64'   { Set-VS2019Environ64 }
				default { Write-Error "internal error"; exit 1 }
			} # switch
		}
		default { Write-Error "internal error"; exit 1 }
	}

	$process = Start-Build -Component 'components' -ProjectFile 'C:/src/components/component1/component1.vcxproj' -Platform $script:Platform -Configuration $script:Config -Mode $script:Mode
	Wait-Process -Target $process

	return 0
}

#--------------------------------------
# エントリーポイント
#--------------------------------------
if ((Initialize) -ne 0) { Finalize; exit 1 }
if ((Main)       -ne 0) { Finalize; exit 1 }
Finalize
exit 0

Powershellで実行して確認します。

Powershellによるビルド①

ビルド結果が「src\build2019」以下に出力されていることも確認して下さい。

PowershellによるMSBuild並列実行

build.ps1のメイン関数を以下のように書き換えます。

src/build.ps1

function Main()
{
	# Visual Studio環境設定
	switch ($script:Develop) {
		'VS2019' {
			switch ($script:Platform) {
				'x64'   { Set-VS2019Environ64 }
				default { Write-Error "internal error"; exit 1 }
			} # switch
		}
		default { Write-Error "internal error"; exit 1 }
	}

	$process1 = Start-Build -Component 'components' -ProjectFile 'C:/src/components/component1/component1.vcxproj' -Platform $script:Platform -Configuration $script:Config -Mode $script:Mode
	$process2 = Start-Build -Component 'components' -ProjectFile 'C:/src/components/component2/component2.vcxproj' -Platform $script:Platform -Configuration $script:Config -Mode $script:Mode
	Wait-Process -Target $process1
	Wait-Process -Target $process2

	return 0
}

Powershellで実行して確認します。

Powershellによるビルド②

ビルド結果が「src\build2019」以下に出力されていることも確認して下さい。
(component1とcomponent2の両方が出力されていることを確認して下さい)

タイトルとURLをコピーしました