Добрый день.
Сделал приложение ClickOnce. Всё хорошо, но утомляет обновлять номер версии. Дело в том, что при выкладывании обновления нужно менять версию как в AssemblyInfo, так и в csproj. Вот так я сделал:
public static class VersionInfo {
public const string VersionString = "1.0.3";
}
А в AssemblyInfo на это свойство ссылаемся:
[assembly: AssemblyVersion(VersionInfo.VersionString)]
[assembly: AssemblyFileVersion(VersionInfo.VersionString)]
Затем нужно залезть в свойства проекта, выбрать вкладку Publish и поменять Publish Version.
Либо руками править файл проекта csproj:
<ApplicationVersion>1.0.3.%2a</ApplicationVersion>
Как я уже написал выше, это утомляет, особенно морально. Захотелось автоматизировать этот процесс, чтобы можно было поменять версию только в одном месте. После чего нажать в меню Build -> Publish, и версия в остальных местах сама обновится. Мне показалось удобным менять значение свойства VersionInfo.VersionString, после чего перед компиляцией свежее значение должно прокинуться в файл проекта. Наверняка можно и по другому, но думаю, варианты решения будут похожи на мой.
Итак, нужно перед компиляцией взять значение из класса VersionInfo и положить его в файл проекта. Подобные махинации вроде должен уметь делать fody, но я не нашёл примера, как он может работать с файлами проектов. Поэтому сделал через MSBuild Task. Задача таски проста — перед компиляцией найти файл с классом VersionInfo, затем вытащить оттуда версию, найти файл проекта, засунуть туда новую версию. По пути поймать ошибки и известить пользователя о них в build output. Вот такой код получился (референсил nuget пакет «Microsoft.Build.Tasks.Core»):
public class PublishVersionSyncTask : Task {
[Required]
public string ProjectFilePath {
get; set;
}
[Required]
public string VersionStringFilePath {
get; set;
}
[Output]
public string Error {
get { return this._error; }
set { this._error = value; }
}
string _error;
public override bool Execute() {
if(!File.Exists(ProjectFilePath)) {
Error = $"Project File "{ProjectFilePath}" does not exists";
return true;
}
if(!File.Exists(VersionStringFilePath)) {
Error = $"Version File "{VersionStringFilePath}" does not exists";
return true;
}
string versionString = null;
var allCodeLines = File.ReadAllLines(VersionStringFilePath);
foreach(var codeLine in allCodeLines) {
if(codeLine.Contains("VersionString")) {
versionString = codeLine.Split('"').Where(s => s.Contains('.')).FirstOrDefault();
break;
}
}
if(String.IsNullOrEmpty(versionString)) {
Error = "Can not find version string.";
return true;
}
if(versionString.Split('.').Length != 3) {
Error = $"Version string has wrong format: {versionString}. It must be x.y.z";
return true;
}
allCodeLines = File.ReadAllLines(ProjectFilePath);
List<string> fixedCodeLines = new List<string>();
foreach(var codeLine in allCodeLines) {
if(!codeLine.Contains("<ApplicationVersion>")) {
fixedCodeLines.Add(codeLine);
continue;
}
if(codeLine.Contains(versionString))
return true;
fixedCodeLines.Add($" <ApplicationVersion>{versionString}.%2a</ApplicationVersion>");
}
try {
if(File.Exists(ProjectFilePath + ".bak"))
File.Delete(ProjectFilePath + ".bak");
}
catch {
Error = $"Can not delete {ProjectFilePath}.bak";
return true;
}
File.Copy(ProjectFilePath, ProjectFilePath + ".bak");
try {
File.Delete(ProjectFilePath);
}
catch {
File.Delete(ProjectFilePath + ".bak");
Error = $"Can not delete {ProjectFilePath}";
return true;
}
File.WriteAllLines(ProjectFilePath, fixedCodeLines);
File.Delete(ProjectFilePath + ".bak");
return true;
}
}
Roslyn умеет работать с файлами проекта «по человечески», но это будет дольше работать. А выполняться будет перед каждой компиляцией (хотя можно сделать так, чтобы в Debug конфигурации этот код не гонялся, но мне это не нужно).
Компилируем, подкладываем библиотеки где-то рядом с папкой целевого проекта. В целевом проекте создаём файл PublishVersionSynchronizer.targets с таким контентом:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="PublishVersionSynchronizer.PublishVersionSyncTask" AssemblyFile="$(TargetDir)....libPublishVersionSynchronizerPublishVersionSynchronizer.dll"/>
<PropertyGroup>
<BuildDependsOn>
PublishVersionSync;
BeforeBuild;
CoreBuild;
AfterBuild
</BuildDependsOn>
</PropertyGroup>
<Target Name="PublishVersionSync">
<PublishVersionSyncTask ProjectFilePath="$(MSBuildProjectFullPath)" VersionStringFilePath="$(MSBuildProjectDirectory)ConfigVersionInfo.cs">
<Output PropertyName="ErrorMessage" TaskParameter="Error" />
</PublishVersionSyncTask>
<Message Text="(out) Publish version patched" Condition="'$(ErrorMessage)' == ''"/>
<Error Condition="'$(ErrorMessage)' != ''" Text="$(ErrorMessage)" />
</Target>
</Project>
Делаем этому файлу BuildAction=«Content», открываем файл проекта, дописываем импорт этого файла:
<Import Project="$(MSBuildToolsPath)Microsoft.CSharp.targets" /><!--После этой строки-->
<Import Project="PublishVersionSynchronizer.targets" />
Делаем этому файлу BuildAction=«Content», открываем файл проекта, дописываем импорт этого файла:
<Import Project="$(MSBuildToolsPath)Microsoft.CSharp.targets" /><!--После этой строки-->
<Import Project="PublishVersionSynchronizer.targets" />
И всё работает.
Если кому-то нужны исходники, они на гитхабе.
Спасибо.
Автор: zed220