Tampons de protocole dans les projets C # utilisant protobuf-net - Meilleures pratiques pour la génération de code
-
19-08-2019 - |
Question
J'essaie d'utiliser protobuf dans un projet C # à l'aide de protobuf-net et je me demande quel est le meilleur moyen de l'organiser dans une structure de projet Visual Studio.
Lorsque vous utilisez manuellement l'outil protogen pour générer du code en C #, la vie semble simple, mais vous ne vous sentez pas bien.
J'aimerais que le fichier .proto soit considéré comme le fichier de code source principal, générant des fichiers C # en tant que sous-produit, mais avant que le compilateur C # n'intervienne.
Les options semblent être:
- Outil personnalisé pour les outils proto (bien que je ne sache pas par où commencer)
- Étape de pré-construction (appel de protogen ou d'un fichier de commandes qui le fait)
J'ai eu des problèmes avec 2) ci-dessus, car il continue à me donner "Le système ne peut pas trouver le fichier spécifié". sauf si j'utilise des chemins absolus (et que je n'aime pas forcer les projets à être explicitement situés).
Existe-t-il (encore) une convention pour cela?
Modifier: Sur la base des commentaires de @ jon, j'ai réessayé la méthode de l'étape de pré-construction et l'ai utilisée (l'emplacement de protogen en code dur pour l'instant), en utilisant l'exemple du carnet d'adresses de Google:
c:\bin\protobuf\protogen "-i:$(ProjectDir)AddressBook.proto"
"-o:$(ProjectDir)AddressBook.cs" -t:c:\bin\protobuf\csharp.xslt
Modifier2: En suivant la recommandation de @ jon de minimiser le temps de compilation en ne traitant pas les fichiers .proto si ceux-ci n'ont pas été modifiés, j'ai créé un outil de base pour vérifier pour moi (cet outil pourrait probablement être étendu à un outil complet de génération personnalisée):
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);
}
}
}
Ma commande de pré-construction est maintenant (sur une seule ligne):
$(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
La solution
En tant qu'extension du code de Shaun, j'ai le plaisir d'annoncer que protobuf-net intègre désormais Visual Studio par le biais d'un outil personnalisé. Le programme d’installation msi est disponible à partir de la page de projet . Des informations plus complètes ici: protobuf-net; maintenant avec les orques ajoutées .
Autres conseils
Appeler une étape de pré-génération mais utiliser des variables de projet (par exemple, $ (ProjectPath)
) pour créer des noms de fichiers absolus sans les avoir réellement dans votre solution me semble être un pari raisonnable.
Une chose que vous voudrez peut-être prendre en compte, selon mon expérience passée des générateurs de code: vous voudrez peut-être écrire un wrapper pour protogen qui génère du code dans un emplacement différent, puis vérifie si le code nouvellement généré est identique à l'ancien. code, et ne l'écrase pas si oui. De cette façon, Visual Studio réalisera que rien n’a changé et ne forcera pas la reconstruction de ce projet, ce qui a permis de réduire considérablement le temps de construction par le passé .
Sinon, vous pouvez conserver un hachage md5 du fichier .proto lors de la dernière exécution de protogen et n'exécuter que protogen si le fichier .proto a été modifié - encore moins à faire pour chaque construction!
Merci d'avoir soulevé cette question en tant que question car elle suggère clairement que je devrais trouver un moyen d'en faire une étape de pré-construction facile pour mon propre port.
Ajoutez l'événement de pré-génération suivant aux paramètres de votre projet pour générer le fichier C # uniquement lorsque le fichier .proto a été modifié. Il suffit de remplacer VotreFichier
par le nom du nom de base de votre fichier .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 }
Cela fonctionne dans toutes les versions récentes de Visual Studio, à la différence de l'outil protobuf-net Custom-Build, qui ne prend pas en charge Visual Studio 2012 ni Visual Studio 2013, en fonction des problèmes 338 et 413 .
Ajoutez ceci au fichier de projet approprié.
Avantage, construction incrémentielle.
Inconvénient, vous devez modifier manuellement lorsque vous ajoutez des fichiers.
<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>
eh bien, ça m’a donné une idée (de réinventer la roue) ...
- créer un Makefile.mak simple, quelque chose comme
.SUFFIXES : .cs .proto .proto.cs: protogen\protogen.exe -i:$? -o:$@ -t:protogen\csharp.xlst
(évidemment, n'oubliez pas de remplacer les chemins d'accès à protogen et csharp.xlst). IMPORTANT - Commande protogen \ protogen.exe à partir du caractère de tabulation (TAB) et non de 8 espaces
- Si vous ne voulez pas spécifier que les fichiers doivent être construits tout le temps, vous pouvez utiliser quelque chose comme
.SUFFIXES : .cs .proto all: mycs1.cs myotherfile.cs .proto.cs: protogen\protogen.exe -i:$? -o:$@ -t:protogen\csharp.xlst
- en phase de pré-construction pour ajouter
cd $(ProjectDir) && "$(DevEnvDir)..\..\vc\bin\nmake" /NOLOGO -c -f Makefile.mak mycs1.cs myotherfile.cs
ou, si vous avez nmake sur votre chemin, on peut utiliser
cd $(ProjectDir) && nmake /NOLOGO -c -f Makefile.mak mycs1.cs myotherfile.cs
J'ai attaché un wrapper rapide et personnalisé de Visual Studio Custom Tool autour de ProtoGen.exe à la page de codes Google de ce problème ( http://code.google.com/p/protobuf-net/issues/detail?id=39 ). Cela facilite l'ajout de fichiers .proto aux projets C #.
Voir le fichier readme en pièce jointe pour plus d'informations.