Буферы протоколов в проектах C# с использованием protobuf-net — лучшие практики генерации кода
-
19-08-2019 - |
Вопрос
Я пытаюсь использовать protobuf в проекте C#, используя protobuf-net, и мне интересно, как лучше всего организовать это в структуру проекта Visual Studio.
При использовании инструмента protogen для генерации кода на C# вручную жизнь кажется легкой, но это не кажется правильным.
Я бы хотел, чтобы файл .proto считался основным файлом исходного кода, создавая файлы C# в качестве побочного продукта, но до того, как будет задействован компилятор C#.
Варианты кажутся такими:
- Пользовательский инструмент для прототипов инструментов (хотя я не понимаю, с чего начать)
- Шаг предварительной сборки (вызов protogen или пакетного файла, который это делает)
Я боролся с 2) выше, поскольку он продолжает выдавать мне «Система не может найти указанный файл», если я не использую абсолютные пути (и мне не нравится заставлять проекты явно определяться).
Есть ли (пока) соглашение по этому поводу?
Редактировать:Основываясь на комментариях @jon, я повторил метод этапа предварительной сборки и использовал его (на данный момент местоположение протогена жестко закодировано), используя пример адресной книги Google:
c:\bin\protobuf\protogen "-i:$(ProjectDir)AddressBook.proto"
"-o:$(ProjectDir)AddressBook.cs" -t:c:\bin\protobuf\csharp.xslt
Редактировать2:Приняв рекомендацию @jon минимизировать время сборки, не обрабатывая файлы .proto, если они не изменились, я собрал базовый инструмент для проверки (вероятно, его можно было бы расширить до полноценного инструмента пользовательской сборки):
using System;
using System.Diagnostics;
using System.IO;
namespace PreBuildChecker
{
public class Checker
{
static int Main(string[] args)
{
try
{
Check(args);
return 0;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return 1;
}
}
public static void Check(string[] args)
{
if (args.Length < 3)
{
throw new ArgumentException(
"Command line must be supplied with source, target and command-line [plus options]");
}
string source = args[0];
string target = args[1];
string executable = args[2];
string arguments = args.Length > 3 ? GetCommandLine(args) : null;
FileInfo targetFileInfo = new FileInfo(target);
FileInfo sourceFileInfo = new FileInfo(source);
if (!sourceFileInfo.Exists)
{
throw new ArgumentException(string.Format(
"Source file {0} not found", source));
}
if (!targetFileInfo.Exists ||
sourceFileInfo.LastWriteTimeUtc > targetFileInfo.LastAccessTimeUtc)
{
Process process = new Process();
process.StartInfo.FileName = executable;
process.StartInfo.Arguments = arguments;
process.StartInfo.ErrorDialog = true;
Console.WriteLine(string.Format(
"Source newer than target, launching tool: {0} {1}",
executable,
arguments));
process.Start();
}
}
private static string GetCommandLine(string[] args)
{
string[] arguments = new string[args.Length - 3];
Array.Copy(args, 3, arguments, 0, arguments.Length);
return String.Join(" ", arguments);
}
}
}
Моя команда предварительной сборки теперь (все в одной строке):
$(SolutionDir)PreBuildChecker\$(OutDir)PreBuildChecker
$(ProjectDir)AddressBook.proto
$(ProjectDir)AddressBook.cs
c:\bin\protobuf\protogen
"-i:$(ProjectDir)AddressBook.proto"
"-o:$(ProjectDir)AddressBook.cs"
-t:c:\bin\protobuf\csharp.xslt
Решение
Как расширение кода Шона, я рад объявить, что protobuf-net теперь имеет интеграцию с Visual Studio с помощью специального инструмента. Установщик msi доступен на странице проекта . Более полная информация здесь: protobuf-net; теперь с добавленными косатками .
Другие советы
Вызов шага перед сборкой, но с использованием переменных проекта (например, $(ProjectPath)
) для создания абсолютных имен файлов без их фактического включения в ваше решение, может показаться разумным выбором.
Одна вещь, которую вы можете рассмотреть, основываясь на моем прошлом опыте генераторов кода: вы можете написать оболочку для protogen, которая генерирует код в другом месте, а затем проверяет, является ли вновь сгенерированный код таким же, как старый код, и не перезаписывать его, если так. Таким образом, Visual Studio поймет, что ничего не изменилось, и не заставит этот проект перестраиваться - это сократило время сборки радикально для меня в прошлом.
Кроме того, вы можете сохранить хэш md5 файла .proto при последнем запуске протогена и выполнять его только в том случае, если файл .proto изменился - даже меньше, чем при каждой сборке!
Спасибо за то, что подняли этот вопрос как вопрос - он явно предлагает мне найти способ сделать этот шаг простым шагом перед сборкой для моего собственного порта.
Добавьте следующее событие предварительной сборки в настройки своего проекта, чтобы генерировать файл C # только после изменения файла .proto. Просто замените YourFile
именем базового имени вашего .proto файла.
cd $(ProjectDir) && powershell -Command if (!(Test-Path YourFile.proto.cs) -or (Get-Item YourFile.proto).LastWriteTimeUtc -gt (Get-Item YourFile.proto.cs).LastWriteTimeUtc) { PathToProtoGen\protogen -i:YourFile.proto -o:YourFile.proto.cs }
Это работает в любой последней версии Visual Studio, в отличие от инструмента Custom-Build protobuf-net, который не поддерживает Visual Studio 2012 или Visual Studio 2013, в соответствии с проблемами 338 и 413 .
Добавьте это в соответствующий файл проекта.
Преимущество, инкрементная сборка.
Недостаток, вам нужно редактировать вручную при добавлении файлов.
<ItemGroup>
<Proto Include="Person.proto" />
<Compile Include="Person.cs">
<DependentUpon>Person.proto</DependentUpon>
</Compile>
</ItemGroup>
<PropertyGroup>
<CompileDependsOn>ProtobufGenerate;$(CompileDependsOn)</CompileDependsOn>
</PropertyGroup>
<Target Name="ProtobufGenerate" Inputs="@(Proto)" Outputs="@(Proto->'$(ProjectDir)%(Filename).cs')">
<ItemGroup>
<_protoc Include="..\packages\Google.Protobuf.*\tools\protoc.exe" />
</ItemGroup>
<Error Condition="!Exists(@(_protoc))" Text="Could not find protoc.exe" />
<Exec Command=""@(_protoc)" "--csharp_out=$(ProjectDir.TrimEnd('\'))" @(Proto->'%(Identity)',' ')" WorkingDirectory="$(ProjectDir)" />
</Target>
ну, это натолкнуло меня на идею (что-то насчет изобретения велосипеда)...
- создайте простой Makefile.mak, что-то вроде
.SUFFIXES : .cs .proto .proto.cs: protogen\protogen.exe -i:$? -o:$@ -t:protogen\csharp.xlst
(очевидно, не забудьте заменить пути к protogen и csharp.xlst). ВАЖНО — команда protogen\protogen.exe начинается с символа TAB, а не с 8 пробелов.
- Если вы не хотите постоянно указывать файлы, которые нужно собирать, вы можете использовать что-то вроде
.SUFFIXES : .cs .proto all: mycs1.cs myotherfile.cs .proto.cs: protogen\protogen.exe -i:$? -o:$@ -t:protogen\csharp.xlst
- на этапе предварительной сборки, чтобы добавить
cd $(ProjectDir) && "$(DevEnvDir)..\..\vc\bin\nmake" /NOLOGO -c -f Makefile.mak mycs1.cs myotherfile.cs
или, если на вашем пути есть nmake, можно использовать
cd $(ProjectDir) && nmake /NOLOGO -c -f Makefile.mak mycs1.cs myotherfile.cs
Я приложил быструю и грязную оболочку Visual Studio Custom Tool вокруг ProtoGen.exe к странице кода Google для этой проблемы ( http://code.google.com/p/protobuf-net/issues/detail?id=39 ). Это делает добавление файлов .proto в проекты C # чрезвычайно простым.
Для получения дополнительной информации см. файл readme во вложении.