MSBuild 16下项目文件使用XmlPoke更新源文件失效问题求助
根因说明
MSBuild 从16.x版本开始新增了项目文件预加载逻辑,会在构建流程启动初期就将所有声明的项目输入文件(包括Xamarin.iOS项目的Info.plist)加载到内存缓存,后续如果是MSBuild内部目标修改了磁盘上的这些文件,默认不会触发缓存刷新,因此后续构建步骤读取到的仍然是修改前的内容。你遇到的问题正是macOS-11镜像自带的MSBuild版本升级到16.x之后触发的,macOS-10.15镜像自带的MSBuild 15.x没有该预加载逻辑,所以可以正常运行。
可行解决方案
方案一:调整自定义目标的执行时机
将你现有修改Info.plist的目标触发时机提前到MSBuild预加载文件之前,替换原有BeforeBuild的配置,改为指定BeforeTargets="PrepareForBuild",修改后的配置如下:
<Target Name="SetIOSVersion" BeforeTargets="PrepareForBuild" Condition=" $(SetVersion) == true "> <PropertyGroup> <IOSBundleVersion>$([System.Text.RegularExpressions.Regex]::Match($(VersionNumber), '[^.][^.]*.[^.]*.[^.]*'))</IOSBundleVersion> </PropertyGroup> <XmlPoke XmlInputPath="Info.plist" Query="//dict/key[. = 'CFBundleVersion']/following-sibling::string[1]" Value="$(VersionNumber)" /> <XmlPoke XmlInputPath="Info.plist" Query="//dict/key[. = 'CFBundleShortVersionString']/following-sibling::string[1]" Value="$(IOSBundleVersion)" /> </Target>
PrepareForBuild是早于文件预加载的执行节点,此时修改的Info.plist内容会被后续缓存加载逻辑正确读取。
方案二:显式声明文件变更
如果调整时机后仍然存在问题,可以在目标配置中追加Inputs和Outputs属性,显式告诉MSBuild该目标会修改Info.plist,强制后续依赖该文件的步骤重新读取磁盘内容:
<Target Name="SetIOSVersion" BeforeTargets="PrepareForBuild" Condition=" $(SetVersion) == true " Inputs="Info.plist" Outputs="Info.plist"> <!-- 原有配置不变 --> </Target>
方案三:CI/CD场景下提前修改文件(最稳妥)
对于Azure DevOps这类CI/CD场景,可以完全绕开MSBuild内部修改的时序问题,在调用Xamarin.iOS构建步骤之前,新增一个bash脚本步骤直接修改Info.plist:
# 写入完整版本号到CFBundleVersion /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $(Application.Version)" Info.plist # 提取短版本号 SHORT_VERSION=$(echo $(Application.Version) | cut -d '.' -f 1,2,3) /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $SHORT_VERSION" Info.plist
该方案无需修改项目文件,不受MSBuild版本变更影响,稳定性最高。
验证方法
你可以在构建流程中增加打印Info.plist内容的步骤,确认修改生效后再启动构建,也可以给MSBuild传入/v:detailed参数开启详细日志,查看Info.plist的加载时机和读取的值,确认修改被正确识别。
内容的提问来源于stack exchange,提问作者TrevB

