Buffers de protocolo en proyectos de C # usando protobuf-net: mejores prácticas para la generación de código

StackOverflow https://stackoverflow.com/questions/453820

Pregunta

Estoy tratando de usar protobuf en un proyecto de C #, usando protobuf-net, y me pregunto cuál es la mejor manera de organizar esto en una estructura de proyecto de Visual Studio.

Cuando se usa manualmente la herramienta protogen para generar código en C #, la vida parece fácil pero no se siente bien.

Me gustaría que el archivo .proto se considere el archivo de código fuente principal, que genera archivos C # como subproducto, pero antes de que el compilador de C # se involucre.

Las opciones parecen ser:

  1. Herramienta personalizada para protoherramientas (aunque no puedo ver por dónde empezar allí)
  2. Paso previo a la compilación (llamar a protogen o un archivo por lotes que lo hace)

He tenido problemas con el 2) anterior, ya que me sigue dando "El sistema no puede encontrar el archivo especificado" a menos que use rutas absolutas (y no me gusta forzar la ubicación explícita de los proyectos).

¿Existe alguna convención (todavía) para esto?


Edición : Basado en los comentarios de @ jon, volví a intentar el método del paso previo a la compilación y usé esto (la ubicación del protogen codificada por ahora), usando el ejemplo de la libreta de direcciones de Google:

c:\bin\protobuf\protogen "-i:$(ProjectDir)AddressBook.proto" 
       "-o:$(ProjectDir)AddressBook.cs" -t:c:\bin\protobuf\csharp.xslt

Editar2: Tomando la recomendación de @ jon para minimizar el tiempo de compilación al no procesar los archivos .proto si no han cambiado, he reunido una herramienta básica para verificar por mí (esto probablemente podría ampliarse a una herramienta de compilación personalizada completa):

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);
        }
    }
}

Mi comando de compilación previa ahora (todo en una línea):

$(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
¿Fue útil?

Solución

Como una extensión del código de Shaun, me complace anunciar que protobuf-net ahora tiene la integración de Visual Studio a través de una herramienta personalizada. El instalador msi está disponible en la página del proyecto . Más información completa aquí: protobuf-net; ahora con Orcas agregadas .

Visual Studio con protobuf-net como Studio visual con protobuf-net una herramienta personalizada

Otros consejos

Llamar a un paso previo a la compilación pero usar variables de proyecto (por ejemplo, $ (ProjectPath) ) para crear nombres de archivo absolutos sin tenerlos realmente en su solución me parecería una apuesta razonable.

Una cosa que quizás desee considerar, en base a mi experiencia previa de generadores de código: es posible que desee escribir un contenedor para el protogen que genere código en una ubicación diferente, luego verifique si el código recién generado es el mismo que el anterior código, y no lo sobrescribe si es así. De esta forma, Visual Studio se dará cuenta de que nada ha cambiado y no forzará la reconstrucción de ese proyecto; esto ha reducido los tiempos de compilación dramáticamente para mí en el pasado.

Alternativamente, puede mantener un hash md5 del archivo .proto la última vez que se ejecutó el protogen, y solo ejecutar el protogen si el archivo .proto ha cambiado, ¡incluso menos que hacer en cada compilación!

Sin embargo, gracias por plantear esto como una pregunta: sugiere claramente que debería encontrar una manera de hacer que este sea un paso fácil de preconstrucción para mi propio puerto.

Agregue el siguiente evento previo a la compilación a la configuración de su proyecto para generar el archivo C # solo cuando el archivo .proto haya cambiado. Simplemente reemplace YourFile con el nombre del nombre base de su archivo .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 }

Esto funciona en cualquier versión reciente de Visual Studio, a diferencia de la herramienta protobuf-net Custom-Build, que no es compatible con Visual Studio 2012 o Visual Studio 2013, según los problemas 338 y 413 .

Agregue esto al archivo de proyecto relevante.

Ventaja, compilación incremental.

Desventaja, debe editar manualmente al agregar archivos.

<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="&quot;@(_protoc)&quot; &quot;--csharp_out=$(ProjectDir.TrimEnd('\'))&quot; @(Proto->'%(Identity)',' ')" WorkingDirectory="$(ProjectDir)" />
</Target>

bueno, eso me dio una idea (algo sobre reinventar la rueda) ...

  • crear Makefile.mak simple, algo así como
.SUFFIXES : .cs .proto

.proto.cs:
    protogen\protogen.exe -i:$? -o:$@ -t:protogen\csharp.xlst

(obviamente, no olvide reemplazar las rutas a protogen y csharp.xlst). IMPORTANTE: comando protogen \ protogen.exe a partir del carácter TAB, no de 8 espacios

  • Si no desea especificar los archivos necesarios para construir todo el tiempo, puede usar algo como
.SUFFIXES : .cs .proto

all: mycs1.cs myotherfile.cs

.proto.cs:
    protogen\protogen.exe -i:$? -o:$@ -t:protogen\csharp.xlst
  • en el paso previo a la compilación para agregar
cd $(ProjectDir) && "$(DevEnvDir)..\..\vc\bin\nmake" /NOLOGO -c -f Makefile.mak mycs1.cs myotherfile.cs

o, si tiene nmake en su camino, uno puede usar  

cd $(ProjectDir) && nmake /NOLOGO -c -f Makefile.mak mycs1.cs myotherfile.cs

He adjuntado un contenedor rápido y sucio de Visual Studio Custom Tool alrededor de ProtoGen.exe a la página de Google Code para este problema ( http://code.google.com/p/protobuf-net/issues/detail?id=39 ). Esto hace que agregar archivos .proto a proyectos de C # sea extremadamente fácil.

Consulte el archivo Léame en el archivo adjunto para obtener más información.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top