VS2015 MSBuild 项目上下文中的 Powershell 变量范围和临时文件
Powershell variable scope and temporary files within the context of a VS2015 MSBuild project
问题
两个独立的 Powershell 脚本,运行 来自两个独立的项目(Chrome 和 Firefox),显然写入同一个临时文件。
这种情况并不经常发生,显然每隔几个月左右发生一次。但是,当它确实发生时,这两个独立的项目每个最终都会将它们的 manifest.json
和 package.json
文件混合在一起,这样每个文件都会有来自另一个文件的随机行散布在其中。
知道为什么会这样吗?
详情
我有一个包含 25 个项目的 VS2015 解决方案。其中一个项目称为 "Deploy",并已声明对多个其他项目的依赖性。对这个问题有重要意义的两个依赖项是 "ChromeExtension" 和 "FirefoxAddOn" 项目。
这些项目中的每一个都包含一个构建目标,用于在适当的清单文件中设置版本信息。这是 Chrome 目标:
<Target Name="SetVersion">
<PropertyGroup>
<PowerShellExe>powershell.exe</PowerShellExe>
<PreBuildScript>$(SolutionDir)powershell\ChromePreBuild.ps1</PreBuildScript>
</PropertyGroup>
<Exec Command="$(PowerShellExe) -NonInteractive -ExecutionPolicy Unrestricted -command "& { &'$(PreBuildScript)' -solutionDir '$(SolutionDir)\' }"" />
</Target>
Firefox 目标是相同的,除了它执行脚本 FirefoxPreBuild.ps1
。
脚本比较简单。 Chrome 脚本:
param($solutionDir)
. "${solutionDir}powershell\BuildConfig.ps1"
. "${solutionDir}powershell\ReplaceVersion.ps1"
. "${solutionDir}powershell\ReplaceName.ps1"
. "${solutionDir}powershell\ChromeOptions.ps1"
Write-Host "Performing pre-build actions for Chrome add-on $chromeExtensionVersion"
ReplaceVersion "${solutionDir}ChromeExtension\manifest.json" $chromeExtensionVersion
ReplaceName "${solutionDir}ChromeExtension\manifest.json" "$chromeExtensionName"
以及 Firefox 脚本:
param($solutionDir)
. "${solutionDir}powershell\BuildConfig.ps1"
. "${solutionDir}powershell\ReplaceVersion.ps1"
Write-Host "Performing pre-build actions for Firefox add-on $firefoxExtensionVersion"
ReplaceVersion "${solutionDir}FirefoxAddOn\package.json" $firefoxExtensionVersion
从两个脚本调用的 ReplaceVersion
函数非常丑陋,但可以完成工作:
function ReplaceVersion {
$file = $args[0]
$replacementVersion = $args[1]
Write-Host "- Updating version number in $file to ""$replacementVersion"""
$tmp = [System.IO.Path]::GetTempFileName();
if(Test-Path $tmp -PathType Leaf) {
Remove-Item $tmp
}
Get-Content $file | Foreach-Object -process {
$line = $_
if ( ($line -match 'AssemblyVersion.*(\d+\.\d+\.\d+\.\d+)') -or
($line -match 'AssemblyFileVersion.*(\d+\.\d+\.\d+\.\d+)') -or
($line -match 'AppFolderName.*(\d+\.\d+\.\d+\.\d+)') -or
($line -match 'SupportedSyncFeatureSetRevision\s*=\s*(\d+);') -or
($line -match 'this.AddinName = ".* v(\d+\.\d+\.\d+\.\d+)";') -or
($line -match 'this.Text = ".* v(\d+\.\d+\.\d+\.\d+)";') -or
($line -match '"version":.*"(\d+\.\d+\.\d+\.?\d*)"') ) {
$version = $matches[1]
$line = $line -replace "$version", "$replacementVersion"
}
$line | Add-Content $tmp
}
#replace the old file with the new one
Remove-Item -force $file
Move-Item $tmp $file -Force -Confirm:$false
}
并且 ReplaceName
函数(Chrome-only)是,缺乏想象力和非 DRY,完全相同的东西,除了匹配不同的模式:
function ReplaceName {
$file = $args[0]
$replacementName = $args[1]
Write-Host "- Updating extension name in $file to ""$replacementName"""
$tmp = [System.IO.Path]::GetTempFileName();
if(Test-Path $tmp -PathType Leaf) {
Remove-Item $tmp
}
Get-Content $file | Foreach-Object -process {
$line = $_
if ( ($line -match '"name":.*"(.*)"') ) {
$name = [regex]::escape($matches[1])
$line = $line -replace "$name", "$replacementName"
}
$line | Add-Content $tmp
}
#replace the old file with the new one
Remove-Item -force $file
Move-Item $tmp $file -Force -Confirm:$false
}
您应该从脚本中删除该部分:
if(Test-Path $tmp -PathType Leaf) {
Remove-Item $tmp
}
所述
Creates a uniquely named, zero-byte temporary file on disk and returns the full path of that file.
[System.IO.Path]::GetTempFileName()
将为您创建空文件,因此您不必删除并 re-create 它。
如果您查看 source of [System.IO.Path]::GetTempFileName()
, then you can see that it implemented by calling to GetTempFileName
WinAPI 函数,其中 uUnique
等于零。这是文档的相关部分:
If uUnique
is zero, the function attempts to form a unique file name using the current system time. If the file already exists, the number is increased by one and the functions tests if this file already exists. This continues until a unique filename is found; the function creates a file by that name and closes it.
如您所见,GetTempFileName
使用文件存在作为信号来创建不同的文件名。通过删除文件,您打开了后续调用可能 return 临时文件的相同文件名的可能性。
问题
两个独立的 Powershell 脚本,运行 来自两个独立的项目(Chrome 和 Firefox),显然写入同一个临时文件。
这种情况并不经常发生,显然每隔几个月左右发生一次。但是,当它确实发生时,这两个独立的项目每个最终都会将它们的 manifest.json
和 package.json
文件混合在一起,这样每个文件都会有来自另一个文件的随机行散布在其中。
知道为什么会这样吗?
详情
我有一个包含 25 个项目的 VS2015 解决方案。其中一个项目称为 "Deploy",并已声明对多个其他项目的依赖性。对这个问题有重要意义的两个依赖项是 "ChromeExtension" 和 "FirefoxAddOn" 项目。
这些项目中的每一个都包含一个构建目标,用于在适当的清单文件中设置版本信息。这是 Chrome 目标:
<Target Name="SetVersion">
<PropertyGroup>
<PowerShellExe>powershell.exe</PowerShellExe>
<PreBuildScript>$(SolutionDir)powershell\ChromePreBuild.ps1</PreBuildScript>
</PropertyGroup>
<Exec Command="$(PowerShellExe) -NonInteractive -ExecutionPolicy Unrestricted -command "& { &'$(PreBuildScript)' -solutionDir '$(SolutionDir)\' }"" />
</Target>
Firefox 目标是相同的,除了它执行脚本 FirefoxPreBuild.ps1
。
脚本比较简单。 Chrome 脚本:
param($solutionDir)
. "${solutionDir}powershell\BuildConfig.ps1"
. "${solutionDir}powershell\ReplaceVersion.ps1"
. "${solutionDir}powershell\ReplaceName.ps1"
. "${solutionDir}powershell\ChromeOptions.ps1"
Write-Host "Performing pre-build actions for Chrome add-on $chromeExtensionVersion"
ReplaceVersion "${solutionDir}ChromeExtension\manifest.json" $chromeExtensionVersion
ReplaceName "${solutionDir}ChromeExtension\manifest.json" "$chromeExtensionName"
以及 Firefox 脚本:
param($solutionDir)
. "${solutionDir}powershell\BuildConfig.ps1"
. "${solutionDir}powershell\ReplaceVersion.ps1"
Write-Host "Performing pre-build actions for Firefox add-on $firefoxExtensionVersion"
ReplaceVersion "${solutionDir}FirefoxAddOn\package.json" $firefoxExtensionVersion
从两个脚本调用的 ReplaceVersion
函数非常丑陋,但可以完成工作:
function ReplaceVersion {
$file = $args[0]
$replacementVersion = $args[1]
Write-Host "- Updating version number in $file to ""$replacementVersion"""
$tmp = [System.IO.Path]::GetTempFileName();
if(Test-Path $tmp -PathType Leaf) {
Remove-Item $tmp
}
Get-Content $file | Foreach-Object -process {
$line = $_
if ( ($line -match 'AssemblyVersion.*(\d+\.\d+\.\d+\.\d+)') -or
($line -match 'AssemblyFileVersion.*(\d+\.\d+\.\d+\.\d+)') -or
($line -match 'AppFolderName.*(\d+\.\d+\.\d+\.\d+)') -or
($line -match 'SupportedSyncFeatureSetRevision\s*=\s*(\d+);') -or
($line -match 'this.AddinName = ".* v(\d+\.\d+\.\d+\.\d+)";') -or
($line -match 'this.Text = ".* v(\d+\.\d+\.\d+\.\d+)";') -or
($line -match '"version":.*"(\d+\.\d+\.\d+\.?\d*)"') ) {
$version = $matches[1]
$line = $line -replace "$version", "$replacementVersion"
}
$line | Add-Content $tmp
}
#replace the old file with the new one
Remove-Item -force $file
Move-Item $tmp $file -Force -Confirm:$false
}
并且 ReplaceName
函数(Chrome-only)是,缺乏想象力和非 DRY,完全相同的东西,除了匹配不同的模式:
function ReplaceName {
$file = $args[0]
$replacementName = $args[1]
Write-Host "- Updating extension name in $file to ""$replacementName"""
$tmp = [System.IO.Path]::GetTempFileName();
if(Test-Path $tmp -PathType Leaf) {
Remove-Item $tmp
}
Get-Content $file | Foreach-Object -process {
$line = $_
if ( ($line -match '"name":.*"(.*)"') ) {
$name = [regex]::escape($matches[1])
$line = $line -replace "$name", "$replacementName"
}
$line | Add-Content $tmp
}
#replace the old file with the new one
Remove-Item -force $file
Move-Item $tmp $file -Force -Confirm:$false
}
您应该从脚本中删除该部分:
if(Test-Path $tmp -PathType Leaf) {
Remove-Item $tmp
}
所述
Creates a uniquely named, zero-byte temporary file on disk and returns the full path of that file.
[System.IO.Path]::GetTempFileName()
将为您创建空文件,因此您不必删除并 re-create 它。
如果您查看 source of [System.IO.Path]::GetTempFileName()
, then you can see that it implemented by calling to GetTempFileName
WinAPI 函数,其中 uUnique
等于零。这是文档的相关部分:
If
uUnique
is zero, the function attempts to form a unique file name using the current system time. If the file already exists, the number is increased by one and the functions tests if this file already exists. This continues until a unique filename is found; the function creates a file by that name and closes it.
如您所见,GetTempFileName
使用文件存在作为信号来创建不同的文件名。通过删除文件,您打开了后续调用可能 return 临时文件的相同文件名的可能性。