Protocol buffers C# projects usando protobuf-net - melhores práticas para geração de código
-
19-08-2019 - |
Pergunta
Eu estou tentando usar o protobuf em um projeto C#, utilizando o protobuf-net, e estou querendo saber qual é a melhor maneira de organizar isso em um projeto do Visual Studio estrutura.
Quando manualmente usando o protogen ferramenta para gerar código em C#, a vida parece fácil, mas não se sente bem.
Eu gostaria .proto arquivo a ser considerado o principal arquivo de código-fonte, gerando C# ficheiros como um produto, mas antes o compilador C# se envolve.
As opções parecem ser:
- Ferramenta personalizada para proto ferramentas (embora eu não posso ver por onde começar lá)
- Pré-etapa de compilação (chamada protogen ou um arquivo de lote que faz isso)
Eu tenho lutado com 2), como ele continua me dando "O sistema não pode encontrar o arquivo especificado" se eu não usar caminhos absolutos (e eu não gosto de forçar projetos explicitamente ser localizado).
Existe uma convenção (ainda) para isso?
Editar: Com base na @jon comentários, eu repetida a pré-etapa de compilação e método utilizado este (protogen a localização codificado por agora), usando o catálogo de endereços, exemplo:
c:\bin\protobuf\protogen "-i:$(ProjectDir)AddressBook.proto"
"-o:$(ProjectDir)AddressBook.cs" -t:c:\bin\protobuf\csharp.xslt
Edit2: Tendo @jon recomendação para minimizar o tempo de compilação por não processar a .proto arquivos se eles não mudaram, eu já bati em conjunto uma ferramenta básica para verificar para mim (isto pode, provavelmente, ser expandida para um full-Custom-ferramenta de Compilação):
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);
}
}
}
O meu pré-comando de compilação é agora (tudo em uma linha):
$(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
Solução
Como uma extensão do código de Shaun, tenho o prazer de anunciar que protobuf-net agora tem integração do Visual Studio por meio de uma ferramenta personalizada. O instalador MSI está disponível no projeto página . Mais informação completa aqui: protobuf-net; agora com adicionado Orcas .
Outras dicas
Chamar uma etapa de pré-build mas usando variáveis ??do projeto (por exemplo $(ProjectPath)
) para criar nomes de arquivo absolutos sem tê-los realmente em sua solução parece uma aposta razoável para mim.
Uma coisa que você pode querer considerar, com base na minha experiência passada de geradores de código: você pode querer escrever um wrapper para protogen que gera código para um local diferente, em seguida, verifica se o código recém-gerado é o mesmo que o antigo código, e não substituí-lo se assim for. Dessa forma Visual Studio vai perceber nada mudou e não forçar esse projeto a ser reconstruída -. Isso tem vezes corte build dramaticamente para mim no passado
Como alternativa, você poderia manter um hash MD5 do arquivo .proto a última vez protogen foi executado, e só executar protogen se o arquivo .proto mudou - ainda menos para fazer em cada build
Obrigado por levantar esta como uma questão embora -. Sugere claramente que eu deveria trabalhar para fora uma maneira de fazer este um passo fácil de pré-compilação para minha própria porta ??p>
Adicione o seguinte evento de pré-compilação para as configurações do projeto para gerar o arquivo C # apenas quando o arquivo .proto mudou. Basta substituir YourFile
com o nome de nome base do seu arquivo .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 }
Isso funciona em qualquer versão recente do Visual Studio, ao contrário da ferramenta Custom-Desenvolver protobuf-net, que não suporta o Visual Studio 2012 ou Visual Studio 2013, de acordo com problemas 338 e 413 .
Adicione esta para o arquivo de projeto relevante.
Advantage, compilação incremental.
Desvantagem, você precisa editar manualmente ao adicionar arquivos.
<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>
bem, isso me deu uma idéia (algo sobre a reinventar a roda)...
- crie simples de Makefile.mak, algo como
.SUFFIXES : .cs .proto .proto.cs: protogen\protogen.exe -i:$? -o:$@ -t:protogen\csharp.xlst
(obviamente, não se esqueça de substituir caminhos para protogen e csharp.xlst). IMPORTANTE - protogen\protogen.exe comando de partida do caractere de TABULAÇÃO, e não 8 espaços
- Se você não deseja especificar os ficheiros necessários para construir o tempo todo, você pode usar algo como
.SUFFIXES : .cs .proto all: mycs1.cs myotherfile.cs .proto.cs: protogen\protogen.exe -i:$? -o:$@ -t:protogen\csharp.xlst
- na pré-etapa de compilação para adicionar
cd $(ProjectDir) && "$(DevEnvDir)..\..\vc\bin\nmake" /NOLOGO -c -f Makefile.mak mycs1.cs myotherfile.cs
ou, se você tiver nmake no seu caminho, pode-se usar
cd $(ProjectDir) && nmake /NOLOGO -c -f Makefile.mak mycs1.cs myotherfile.cs
Anexei um invólucro Estúdio Custom Tool Visual rápida e suja ao redor ProtoGen.exe para a página do Google Code para este problema ( http://code.google.com/p/protobuf-net/issues/detail?id=39 ). Isso faz com que a adição de .proto arquivos para projetos C # extremamente fácil.
Veja o readme no anexo para mais informações.